「活動資料回傳」會把你指定專案內的填答與集點卡活動,傳到你的 CRM、會員系統或兌獎系統。不需要會員資料空間也能建立;有沒有會員資料空間,決定的是能收到哪些事件。每組回傳都有獨立簽章金鑰,不會使用帳號 API Key。
目前「活動資料回傳」僅開放給 Beta 測試名單中的帳號;尚未加入的帳號請繼續使用「Webhooks」,想參加測試請與我們聯繫。
開始前先準備
- 一個可公開連線的 HTTPS 443 接收網址。網址不可導向內網,也不可重新導向。
- 接收系統可以保存
eventId,並在同一筆交易中完成去重與業務寫入。 - 只想接收「回答完成」事件:不需要會員資料空間,直接跳到下一節建立即可。
- 想接收「任務完成」「實際加點」「平台兌換」,或想讓同一位玩家跨測驗被認成同一人:先建立會員資料空間與會員登入連線,並讓至少一個專案啟用該登入。
建立一組活動資料回傳
- 前往「帳號設定」的「開發人員專區」。
- 在「活動資料回傳」選擇這組回傳要用的身分:選一個會員資料空間,或選「不綁會員身分」。
- 填入名稱與 HTTPS 443 接收網址。
- 選擇「新格式(建議)」或「既有 Webhook 格式」。格式建立後不能切換。需要另一種格式時,請建立另一組回傳。
- 勾選事件與允許回傳的專案。選「不綁會員身分」時只有「回答完成」會真的送出,其餘三種事件需要會員資料空間。
- 建立後立即保存簽章金鑰。畫面只會顯示一次。
- 按「傳送測試事件」,確認狀態顯示「已送達」。HTTP 200 只代表接收端收到資料,不代表已完成發券、加點或其他處理。
可選事件有「回答完成」、「任務完成」、「實際加點」與「平台兌換」。活動資料回傳不提供「開始填答」。選「不綁會員身分」時,事件的身分是依每筆填答各自產生的匿名代碼,同一個人填兩次會被視為兩筆、無法跨測驗辨識為同一人;要跨測驗辨識同一人,需要會員資料空間提供的會員身分。
依你的活動選事件
- Maggie 經營美妝品牌測驗,還沒建立會員資料空間:直接選「不綁會員身分」、勾「回答完成」,用
answerId連回測驗結果,再依玩家偏好分眾。 - Kevin 代營兩關集點活動,需要「任務完成」與「實際加點」:先建立會員資料空間並選它當這組回傳的身分,接收端分別記錄完成憑證與實際入帳的點數。
- Daniel 經營會員日領券,選「回答完成」與「平台兌換」:同樣要用會員資料空間,先以
eventId去重,再由自己的會員系統決定是否發券。
新格式的資料與簽章
新格式會傳送完整的權威事件。以下是回答完成事件的結構:
{
"brandId": "brand_123",
"campaignId": "campaign_123",
"eventId": "64-character-sha256-event-id",
"externalUserId": "brand:v2:canonical-member-id",
"occurredAt": "2026-09-15T08:00:00.000Z",
"payload": { "answerId": "answer_123" },
"schemaVersion": 2,
"source": {
"firebaseProject": "ooopen-project",
"path": "answers/answer_123",
"id": "answer_123"
},
"sourceProjectId": "quiz_123",
"type": "finish",
"webhookConnectionId": "connection_123"
}
選「不綁會員身分」時,brandId 固定是保留值 __no-brand__、campaignId 固定是 __no-campaign__,externalUserId 則是依 answerId 衍生的匿名代碼(anon:v1:...)。三者都不是真的品牌、活動或會員資料,請當成「不適用」處理。
先讀取未解析的 request body。將 X-OOOPEN-Webhook-Timestamp、一個半形句點與完整原始 body bytes 串接,再用該組回傳的簽章金鑰計算 HMAC-SHA256。輸出採 base64url。時間戳記超過五分鐘時,請拒絕請求。
const signedBytes = Buffer.concat([
Buffer.from(timestamp + ".", "utf8"),
rawBody
]);
const expected = crypto
.createHmac("sha256", signingSecret)
.update(signedBytes)
.digest("base64url");
回答完成事件的 payload 有哪些欄位
payload 只放答案紀錄裡、在完成那一刻就已經存在的欄位,不會另外去讀當時的測驗內容——不含測驗名稱、結果名稱、題目或選項文字。這是因為這包資料簽了章、可以被重送:如果重送時去讀「現在」的測驗內容,創作者事後改了測驗,舊事件重送出來的就會變成假的歷史紀錄。需要顯示名稱時,請自行用 quizId 與 result 打 Creator Data API 的 /api/v1/quizzes/{quizId}/answers/{answerId} 查詢。
answerId:這筆填答的 ID,一定會有。quizId:這筆填答屬於哪個專案,一定會有。startedAt:玩家開始作答的時間(不是完成時間),有值才會出現。ref:填答紀錄上的來源代碼,有值才會出現。result:結果代碼(不是結果名稱),有值才會出現。answers:玩家送出的答案,依原始格式提供,有值才會出現。answersTruncated:內容過大被省略時會是true;出現這個欄位時不會同時出現answers。utm:來源歸因資料,只有你的帳號開通「UTM 匯出」這個加購功能時才會出現。
不會有 email 等聯絡資料,utm.code(兌換碼)也不會出現。
既有 Webhook 格式的資料與簽章
如果接收系統需要 data、type、timestamp 外層,可選「既有 Webhook 格式」。這個選項仍使用新連線的獨立金鑰,不會呼叫原本的 Webhook,也不會補造舊版的卡片、會員或餘額欄位。身分保留值規則與新格式相同:不綁會員身分時 brandId / campaignId 分別固定為 __no-brand__ / __no-campaign__。
{
"data": {
"wireSchema": "ooopen-brand-activity-webhook-legacy-envelope/1",
"eventId": "64-character-sha256-event-id",
"type": "finish",
"occurredAt": "2026-09-15T08:00:00.000Z",
"brandId": "brand_123",
"sourceProjectId": "quiz_123",
"campaignId": "campaign_123",
"webhookConnectionId": "connection_123",
"externalUserId": "brand:v2:canonical-member-id",
"payload": { "answerId": "answer_123" }
},
"type": "finish",
"timestamp": "2026-09-15T08:00:00.000Z"
}
用收到的 data 原始 JSON bytes 與簽章金鑰計算 HMAC-SHA256,輸出小寫 hex,並比對 x-ooopenlab-signature。驗證成功後,確認外層 type 等於 data.type,外層 timestamp 等於 data.occurredAt。最後檢查 wireSchema 與事件名稱。
const expected = crypto
.createHmac("sha256", signingSecret)
.update(rawDataBytes)
.digest("hex");
接收端必須先去重
OOOPEN Lab 可能重試暫時失敗的請求。手動重送也會保留原本的 body、eventId、deliveryId 與格式。請依下列順序處理:
- 驗證簽章與格式。
- 在資料庫交易內查詢
eventId。 - 如果已處理,直接回 HTTP 200,不要再次發券或加點。
- 如果未處理,在同一筆交易寫入
eventId與業務結果。 - 交易完成後回 HTTP 200。
若狀態是「結果不明」,接收端可能已處理該事件。請先查詢 eventId,再決定是否手動重送。重送不會改成另一種格式。
更換金鑰與停用連線
更換簽章金鑰後,舊金鑰立即失效。先更新接收端,再用新金鑰傳送測試事件。停用連線會停止後續投遞,已停用的連線不能測試或重送。
從既有 Webhook 切換
既有 Webhook 是帳號層級:預設涵蓋這個帳號底下所有測驗(也可以額外指定只送單一測驗),不用逐一勾選專案。活動資料回傳則要求建立時明確勾選要送出的專案,一組回傳最多勾 50 個。既有 Webhook 有「開始填答」事件,活動資料回傳完全不提供這個事件——換過去之後它會直接消失,不會用別的事件取代。原本的 start 事件也無法升級到活動資料回傳。
簽章金鑰換了,切換期間建議用新網址
既有 Webhook 用帳號的 API Key 簽章。活動資料回傳選「既有 Webhook 格式」時,header 名稱一樣是 x-ooopenlab-signature,但簽章金鑰換成這組回傳自己的簽章金鑰,不是帳號 API Key。如果接收端還在用帳號 API Key 驗這個 header,新格式送來的每一筆都會驗章失敗;如果切換期間讓新舊 Webhook 同時對著同一個網址送資料、接收端又只認一把金鑰,兩邊一定有一邊會驗章失敗。
如果真的需要在同一個接收端同時處理新舊資料,可以先看 body 分辨:新格式的「既有 Webhook 格式」body,data 物件裡一定有 wireSchema 與 eventId 這兩個欄位;既有 Webhook 送出的資料完全沒有這兩個欄位。但更簡單、更不容易出錯的做法,是切換期間幫新的活動資料回傳連線設一個新的接收網址,不要跟既有 Webhook 共用同一個網址——這樣兩邊各自驗自己的金鑰,不用在同一支程式裡判斷「這筆資料是新是舊」。等新連線確認運作正常,再回到「既有 Webhook(僅管理現有設定)」刪除舊設定。
選「新格式」不會遇到這個問題:它固定送三個獨立的 header(X-OOOPEN-Webhook-Timestamp、X-OOOPEN-Webhook-Event-Id、X-OOOPEN-Webhook-Signature),簽章是用時間戳記接原始 body 算出來的,且時間戳記超過五分鐘會被拒絕,不會跟既有 Webhook 的 header 搞混。
切換步驟
- 先讓接收端依新格式驗章,並依
eventId去重。切換期間若同時收舊資料,請另以answerId去重。 - 在「活動資料回傳」重新選擇這組回傳要用的身分(會員資料空間或不綁會員身分)、專案、事件與格式。
- 保存新金鑰並完成測試。
- 先啟用新連線,再觀察接收紀錄。
- 確認資料正常後,回到「既有 Webhook(僅管理現有設定)」刪除舊設定。
切換不能保證完全沒有重複事件。先完成接收端去重,再停止舊設定。