การส่งข้อมูลกิจกรรมจะส่งกิจกรรมจากโปรเจกต์ที่คุณเลือกไปยัง CRM ระบบสมาชิก หรือระบบแลกรางวัล ไม่จำเป็นต้องมีพื้นที่ข้อมูลสมาชิกก็สร้างได้ การมีหรือไม่มีพื้นที่ข้อมูลสมาชิกเป็นตัวกำหนดว่าจะรับเหตุการณ์ใดได้บ้าง การส่งแต่ละชุดมีคีย์ลงนามแยกและไม่ใช้ API Key ของบัญชี
ขณะนี้การส่งข้อมูลกิจกรรมเปิดให้เฉพาะบัญชีเบต้าเท่านั้น หากบัญชีของคุณยังไม่อยู่ในรายชื่อเบต้า โปรดใช้ Webhook ต่อไป และติดต่อเราหากต้องการเข้าร่วมทดสอบ
สิ่งที่ต้องเตรียม
- เตรียมปลายทางสาธารณะแบบ HTTPS พอร์ต 443 ซึ่งต้องไม่ redirect หรือชี้ไปเครือข่ายส่วนตัว
- ระบบรับต้องบันทึก
eventIdและทำทั้งการป้องกันข้อมูลซ้ำกับการเขียนผลธุรกิจใน transaction เดียวกันได้ - ต้องการรับเฉพาะเหตุการณ์ตอบเสร็จ: ไม่ต้องมีพื้นที่ข้อมูลสมาชิก ข้ามไปสร้างการเชื่อมต่อได้เลย
- ต้องการภารกิจเสร็จ เพิ่มแต้มจริง หรือแลกรางวัลบนแพลตฟอร์ม หรือต้องการให้ระบุผู้เล่นคนเดียวกันข้ามโปรเจกต์ได้: ต้องสร้างพื้นที่ข้อมูลสมาชิกและการเชื่อมต่อเข้าสู่ระบบสมาชิกก่อน แล้วเปิดใช้การเข้าสู่ระบบนั้นในอย่างน้อยหนึ่งโปรเจกต์
สร้างการส่งข้อมูลกิจกรรม
- เปิดการตั้งค่าบัญชี แล้วไปที่พื้นที่นักพัฒนา
- ในหัวข้อการส่งข้อมูลกิจกรรม เลือกว่าการเชื่อมต่อนี้จะใช้ตัวตนแบบใด: พื้นที่ข้อมูลสมาชิก หรือไม่ผูกตัวตนสมาชิก
- ใส่ชื่อและปลายทาง HTTPS พอร์ต 443
- เลือกรูปแบบใหม่ (แนะนำ) หรือรูปแบบ Webhook เดิม เปลี่ยนรูปแบบหลังสร้างไม่ได้ หากต้องการรูปแบบอื่นให้สร้างการเชื่อมต่อใหม่
- เลือกเหตุการณ์และโปรเจกต์ที่อนุญาตให้ส่ง หากเลือกไม่ผูกตัวตนสมาชิก จะมีเพียงเหตุการณ์ตอบเสร็จเท่านั้นที่ถูกส่งจริง ส่วนอีกสามเหตุการณ์ต้องมีพื้นที่ข้อมูลสมาชิก
- สร้างการเชื่อมต่อและบันทึกคีย์ลงนามทันที หน้าจอจะแสดงเพียงครั้งเดียว
- ส่งเหตุการณ์ทดสอบและตรวจสอบว่าสถานะเป็นส่งถึงแล้ว HTTP 200 ยืนยันเฉพาะการรับส่ง ไม่ได้ยืนยันการออกคูปอง เพิ่มแต้ม หรืองานธุรกิจอื่น
เหตุการณ์มีสี่แบบ: ตอบเสร็จสิ้น ภารกิจเสร็จสิ้น เพิ่มแต้มจริง และแลกรางวัลบนแพลตฟอร์ม โดยไม่มีเหตุการณ์เริ่มตอบ เมื่อเลือกไม่ผูกตัวตนสมาชิก ตัวตนของแต่ละเหตุการณ์จะเป็นรหัสไม่ระบุตัวตนที่สร้างจากการตอบแต่ละครั้ง ทำให้คนเดียวกันตอบสองครั้งถูกนับเป็นสองเหตุการณ์ที่ไม่เกี่ยวข้องกัน และไม่สามารถระบุได้ว่าเป็นคนเดียวกันข้ามโปรเจกต์ การระบุคนเดียวกันข้ามโปรเจกต์ต้องอาศัยตัวตนสมาชิกจากพื้นที่ข้อมูลสมาชิกเท่านั้น
Maggie ทำแบบทดสอบแบรนด์ความงามและยังไม่มีพื้นที่ข้อมูลสมาชิก เธอเลือกไม่ผูกตัวตนสมาชิก เปิดเฉพาะเหตุการณ์ตอบเสร็จ แล้วใช้ answerId แบ่งกลุ่มผู้เล่นตามผลลัพธ์ Kevin ทำกิจกรรมสะสมแต้มสองด่านและต้องการภารกิจเสร็จกับเพิ่มแต้มจริง เขาสร้างพื้นที่ข้อมูลสมาชิกก่อนแล้วเลือกเป็นตัวตนของการเชื่อมต่อนี้ เพื่อให้ระบบรับบันทึกหลักฐานความสำเร็จและแต้มที่เข้าจริงแยกกัน ส่วน Daniel ทำกิจกรรมวันสมาชิกแจกคูปอง เปิดตอบเสร็จกับแลกรางวัลบนแพลตฟอร์ม เขาก็ต้องมีพื้นที่ข้อมูลสมาชิกเช่นกัน ป้องกันข้อมูลซ้ำด้วย eventId ก่อน แล้วให้ระบบสมาชิกของตนตัดสินใจว่าจะออกคูปองหรือไม่
Body และลายเซ็นของรูปแบบใหม่
รูปแบบใหม่ส่งเหตุการณ์ต้นฉบับที่ผ่านการยืนยันครบถ้วน:
{
"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 ก่อน parse แล้วต่อ X-OOOPEN-Webhook-Timestamp จุดหนึ่งตัว และ raw body bytes ทั้งหมด คำนวณ HMAC-SHA256 ด้วยคีย์ลงนามของการเชื่อมต่อและเข้ารหัสแบบ base64url ปฏิเสธ timestamp ที่เก่ากว่าห้านาที เปรียบเทียบกับ X-OOOPEN-Webhook-Signature และตรวจว่า X-OOOPEN-Webhook-Event-Id ตรงกับ eventId ใน body
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เมื่อเนื้อหาถูกตัดออกเพราะขนาดใหญ่เกินไป จะไม่ปรากฏพร้อมกับanswersutm: ข้อมูลที่มา จะปรากฏเฉพาะเมื่อบัญชีของคุณเปิดใช้ฟีเจอร์เสริม UTM export
จะไม่มีข้อมูลติดต่ออย่างอีเมลปรากฏอยู่เลย และ utm.code (รหัสแลกรับ) ก็จะถูกงดไว้เช่นกัน
Body และลายเซ็นของรูปแบบ Webhook เดิม
เลือกรูปแบบ Webhook เดิมเมื่อระบบรับต้องใช้ envelope ที่มี data, type และ timestamp เท่านั้น รูปแบบนี้ยังใช้คีย์แยกของการเชื่อมต่อใหม่ OOOPEN Lab จะไม่เรียก 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"
}
คำนวณ HMAC-SHA256 จาก JSON bytes ของ data ที่ serialize ตรงตามที่ได้รับ เข้ารหัสเป็น hex ตัวพิมพ์เล็ก แล้วเปรียบเทียบกับ x-ooopenlab-signature หลังตรวจลายเซ็น ให้ตรวจว่า type ด้านนอกเท่ากับ data.type และ timestamp เท่ากับ data.occurredAt จากนั้นตรวจ wireSchema ชนิดเหตุการณ์ และ payload ตามชนิด ห้ามใช้ตัวตรวจลายเซ็นของสองรูปแบบสลับกัน
ป้องกันข้อมูลซ้ำก่อนทำงานธุรกิจ
- ตรวจลายเซ็นและรูปแบบ
- ค้นหา
eventIdภายใน database transaction - หากเคยประมวลผลแล้ว ให้ตอบ HTTP 200 โดยไม่ออกคูปองหรือเพิ่มแต้มซ้ำ
- หากยังไม่เคยประมวลผล ให้บันทึก
eventIdและผลธุรกิจใน transaction เดียวกัน - ตอบ HTTP 200 หลัง transaction สำเร็จเท่านั้น
การลองใหม่อัตโนมัติและการส่งซ้ำด้วยตนเองจะคง body, eventId, deliveryId และรูปแบบเดิม สถานะไม่ทราบผลหมายถึงระบบรับอาจประมวลผลแล้ว ให้ตรวจ eventId ก่อนส่งซ้ำ และส่งซ้ำได้เฉพาะในช่วงที่ยังเก็บบันทึกการส่ง
เปลี่ยนคีย์หรือปิดการเชื่อมต่อ
เมื่อเปลี่ยนคีย์ลงนาม คีย์เก่าจะใช้ไม่ได้ทันที อัปเดตระบบรับแล้วส่งเหตุการณ์ทดสอบด้วยคีย์ใหม่ การเชื่อมต่อที่ปิดแล้วจะทดสอบ ส่งเหตุการณ์ หรือส่งซ้ำไม่ได้
ย้ายจาก Webhook เดิม
Webhook เดิมทำงานระดับบัญชีโดยค่าเริ่มต้น: จะยิงทุกแบบทดสอบในบัญชีนี้โดยอัตโนมัติ เว้นแต่จะระบุให้ส่งเฉพาะแบบทดสอบเดียว ส่วนการส่งข้อมูลกิจกรรมต้องเลือกโปรเจกต์ที่อนุญาตให้ส่งอย่างชัดเจนตอนสร้าง สูงสุด 50 โปรเจกต์ต่อการเชื่อมต่อ Webhook เดิมมีเหตุการณ์เริ่มตอบ แต่การส่งข้อมูลกิจกรรมไม่มีเหตุการณ์นี้เลย เมื่อย้ายมาเหตุการณ์นี้จะหายไปโดยไม่มีเหตุการณ์อื่นมาแทน
คีย์ลงนามเปลี่ยนไปแล้ว ช่วงเปลี่ยนระบบแนะนำให้ใช้ปลายทางใหม่
Webhook เดิมลงนามด้วย API Key ของบัญชี เมื่อการเชื่อมต่อการส่งข้อมูลกิจกรรมใช้รูปแบบ Webhook เดิม ชื่อ header ยังคงเป็น x-ooopenlab-signature เหมือนเดิม แต่คีย์ลงนามเปลี่ยนเป็นคีย์ของการเชื่อมต่อนั้นเอง ไม่ใช่ API Key ของบัญชี หากระบบรับยังตรวจ header นี้ด้วย API Key ของบัญชี การส่งข้อมูลทุกครั้งจากการเชื่อมต่อใหม่จะตรวจลายเซ็นไม่ผ่าน และถ้าช่วงเปลี่ยนระบบทั้ง Webhook เดิมและการเชื่อมต่อใหม่ส่งไปที่ปลายทางเดียวกัน โดยระบบรับเชื่อคีย์เพียงตัวเดียว จะมีฝั่งใดฝั่งหนึ่งตรวจไม่ผ่านแน่นอน
หากจำเป็นต้องรับทั้งสองแบบที่ปลายทางเดียว ให้แยกจาก body: การส่งแบบ Webhook เดิมรูปแบบใหม่จะมี wireSchema และ eventId อยู่ใน data เสมอ ส่วน payload ของ Webhook เดิมไม่มีทั้งสองฟิลด์นี้เลย วิธีที่ง่ายและปลอดภัยกว่าคือกำหนดปลายทางใหม่ให้การเชื่อมต่อการส่งข้อมูลกิจกรรม แทนที่จะใช้ URL เดียวกับ Webhook เดิมในช่วงเปลี่ยนระบบ แต่ละฝั่งจะตรวจคีย์ของตัวเองแยกกัน ระบบรับไม่ต้องตัดสินใจในโค้ดเดียวกันว่า "อันนี้เก่าหรือใหม่" ลบการตั้งค่า Webhook เดิม (จัดการเฉพาะที่มีอยู่) หลังยืนยันว่าการเชื่อมต่อใหม่ทำงานถูกต้องแล้วเท่านั้น
การเลือกรูปแบบใหม่จะไม่เจอปัญหานี้เลย เพราะส่ง header แยกสามตัวเสมอ (X-OOOPEN-Webhook-Timestamp, X-OOOPEN-Webhook-Event-Id, X-OOOPEN-Webhook-Signature) ลายเซ็นคำนวณจาก timestamp ต่อกับ raw body และปฏิเสธ timestamp ที่เก่ากว่าห้านาที จึงไม่ปนกับ header ของ Webhook เดิม
ขั้นตอนการย้ายระบบ
- เพิ่มตัวตรวจลายเซ็นใหม่และการป้องกันข้อมูลซ้ำด้วย
eventIdแบบ transaction ในระบบรับ หากข้อมูลเก่าอาจซ้อนกัน ให้ป้องกันซ้ำด้วยanswerIdด้วย - เลือกตัวตน (พื้นที่ข้อมูลสมาชิก หรือไม่ผูกตัวตนสมาชิก) โปรเจกต์ เหตุการณ์ และรูปแบบใหม่ในการส่งข้อมูลกิจกรรม
- บันทึกคีย์ใหม่และทดสอบให้ผ่าน
- เปิดการเชื่อมต่อใหม่ก่อนแล้วตรวจบันทึกการรับ
- ลบการตั้งค่าเก่าหลังยืนยันว่าการส่งใหม่ทำงาน
ช่วงเปลี่ยนระบบไม่สามารถรับประกันว่าจะไม่มีเหตุการณ์ซ้ำ ต้องทำการป้องกันข้อมูลซ้ำที่ระบบรับให้เสร็จก่อนหยุดการตั้งค่าเก่า