Webhook
รูปแบบ payload, header, ลายเซ็น, นโยบายการส่งซ้ำ และการรับมือกับการส่งซ้ำซ้อน
สร้างจาก public-api.md โดยอัตโนมัติเวอร์ชัน 1.2.0ปรับปรุงล่าสุด 2026-09-20
#Webhook
เมื่อคุณระบุ webhook_url ในรายการรับเงินหรือจ่ายเงินออก ระบบจะ POST ความคืบหน้าของรายการ
ไปยัง URL นั้น
webhook_urlกำหนดเป็นรายคำขอ API นี้ไม่มีวิธีลงทะเบียน webhook URL เริ่มต้นไว้ล่วงหน้า ระบบมีการตั้งค่า webhook แบบบันทึกไว้อยู่จริง (POST /v1/webhooks/config) แต่เรียกใช้ได้ ด้วยการล็อกอินแดชบอร์ดเท่านั้น (IAM JWT) ไม่ได้เปิดบนเกตเวย์ HMAC สาธารณะ และปัจจุบัน ควบคุมเฉพาะ event กลุ่มreport.*ส่วน webhook ของรายการธุรกรรมจะใช้webhook_urlที่คุณใส่มากับคำขอนั้น ๆ เสมอ หากไม่ใส่ ระบบจะไม่ส่ง webhook เลย
#webhook ถูกส่งเมื่อใด
webhook ของรายการธุรกรรมจะถูกส่งเมื่อรายการเข้าสู่สถานะ completed, failed,
expired หรือ refunded และจะไม่มี webhook สำหรับ pending, processing
หรือ pending_approval
#รูปแบบข้อมูล webhook ของรายการธุรกรรม
payload ของ webhook รายการธุรกรรมเป็นโครงสร้างแบน ไม่ได้ ห่อด้วยซองของ event:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "deposit",
"amount": 1500.00,
"paidAmount": 1500.42,
"fee": 37.50,
"netAmount": 1462.50,
"bankAccountNo": "1234567890",
"bankAccountName": "SOMCHAI JAIDEE",
"bankCode": "014",
"bankName": "ธนาคารไทยพาณิชย์",
"currency": "THB",
"referenceNo": "ORD888888888",
"status": "success",
"timestamp": "2026-09-20T17:35:00+07:00",
"paidAt": "2026-09-20T17:34:55.123456789+07:00"
}#ฟิลด์ใน payload ของ webhook
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
id | string | รหัสรายการ |
type | string | deposit หรือ withdraw |
amount | number | จำนวนเงินที่ขอไว้ตอนแรก |
paidAmount | number | จำนวนเงินที่โอนจริง (ยอดสตางค์ไม่ซ้ำที่อยู่ใน QR) เป็น 0 เว้นแต่ status เป็น success |
fee | number | ค่าธรรมเนียมที่เรียกเก็บ |
netAmount | number | amount - fee |
bankAccountNo | string | บัญชีผู้ชำระเงิน (รับเงิน) หรือบัญชีปลายทาง (จ่ายเงินออก) |
bankAccountName | string | ชื่อเจ้าของบัญชี |
bankCode | string | รหัสธนาคาร |
bankName | string | ชื่อธนาคารภาษาไทย แปลงจาก bankCode (หากไม่รู้จักจะคืนตัวรหัสกลับมา) |
currency | string | รหัสสกุลเงิน |
referenceNo | string | ค่า reference_id ของคุณ |
status | string | success, failed, expired หรือ refunded |
timestamp | string | เวลาของ ความพยายามส่งครั้งนี้ (RFC 3339 เวลาไทย) เปลี่ยนทุกครั้งที่ส่งซ้ำ |
paidAt | string | เวลาที่ชำระเงินสำเร็จ (RFC 3339 ระดับนาโนวินาที) เป็นข้อความว่างหากยังไม่ชำระ |
failureReason | string | มีเฉพาะเมื่อ status เป็น failed หรือ refunded |
#ค่า status ของ webhook และ event ที่คู่กัน
status | X-Webhook-Event | สถานะของรายการ |
|---|---|---|
success | transaction.completed | completed |
failed | transaction.failed | failed |
expired | transaction.expired | expired |
refunded | transaction.refunded | refunded |
expired เป็นค่าที่ใช้งานจริง ไม่ใช่ชื่อเก่าที่แทน failed ส่วน webhook แบบ refunded
หมายความว่ารายการที่เคยรายงานว่า success ถูกกลับรายการแล้ว ให้ยกเลิกการเข้ายอดของคำสั่งซื้อนั้น
โดยค่า failureReason จะบอกเหตุผลจากผู้ดูแลระบบ
#event ของรายงาน (รูปแบบต่างออกไป)
report.daily_summary ซึ่งเป็นสรุปยอดคงเหลือวันละครั้ง ส่งผ่านช่องทางเดียวกัน ใช้การเซ็น
และกติกาการส่งซ้ำแบบเดียวกัน แต่ body ของมัน เป็น ซองของ event:
{
"id": "daily_summary:<merchant_id>:2026-09-19",
"event": "report.daily_summary",
"merchant_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"data": { "...": "the daily-summary report body" },
"timestamp": "2026-09-20T21:35:23+07:00"
}แยกสองรูปแบบนี้ออกจากกันด้วย header X-Webhook-Event หรือดูว่ามีฟิลด์ event/data
หรือไม่ รายละเอียดของ event กลุ่มรายงานอยู่ใน
merchant-reports-api.md และการสมัครรับ event เหล่านี้
ปัจจุบันต้องทำผ่านการล็อกอินแดชบอร์ด
#header ของ webhook
| Header | คำอธิบาย |
|---|---|
Content-Type | application/json |
X-Webhook-Signature | ลายเซ็น HMAC-SHA256 เลขฐานสิบหกตัวพิมพ์เล็ก ดูด้านล่าง |
X-Webhook-Timestamp | เวลา Unix (วินาที) ของความพยายามส่งครั้งนี้ |
X-Webhook-Event | transaction.completed / .failed / .expired / .refunded หรือ report.daily_summary |
X-Transaction-ID | รหัสรายการ หรือสำหรับ event ของรายงานคือกุญแจการนำส่งแบบคงที่ |
ระบบไม่มี header X-Webhook-ID และไม่ได้ตั้ง User-Agent เฉพาะ
#การตรวจสอบ webhook
X-Webhook-Signature = lowercase_hex(HMAC-SHA256(SecretKey, X-Webhook-Timestamp + "|" + raw_body))กุญแจที่ใช้เซ็นคือ secret key ของ API ของคุณ — ค่า sk_… ตัวเดียวกับที่ใช้เซ็นคำขอ
ไม่ใช่ ฟิลด์ secret ที่ endpoint ตั้งค่า webhook ของแดชบอร์ดคืนมา (ค่านั้นไม่ได้ใช้เซ็น)
ให้ตรวจสอบกับ ไบต์ดิบ ที่คุณได้รับ อย่าใช้สำเนาที่แปลงเป็นข้อความใหม่:
import hmac
import hashlib
def verify_webhook(raw_body: bytes, signature: str, timestamp: str, secret_key: str) -> bool:
expected = hmac.new(
secret_key.encode(),
timestamp.encode() + b"|" + raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)#นโยบายการส่งซ้ำของ webhook
- ถือว่าส่งสำเร็จเมื่อได้รับ response 2xx ใดก็ได้ภายใน 10 วินาที
- หากส่งไม่สำเร็จจะลองใหม่ทุก 1 นาที เป็นช่วงคงที่ (ไม่ใช่แบบทวีคูณ แม้การตั้งค่า
webhook ในแดชบอร์ดจะแสดงป้ายว่า
retry_backoff: "exponential"ก็ตาม) - ลองทั้งหมด 5 ครั้ง ตามค่าเริ่มต้น หลังจากนั้นจะถูกบันทึกว่าส่งไม่สำเร็จและไม่ลองอีก
- ตัวส่งซ้ำทำงานรอบละหนึ่งนาที ระยะห่างจริงระหว่างการส่งแต่ละครั้งจึงอยู่ราว 1–2 นาที
#การส่งซ้ำซ้อน — ทำให้ตัวรับของคุณรองรับการทำซ้ำได้
webhook ของรายการธุรกรรมรับประกันว่าส่งอย่างน้อยหนึ่งครั้ง และไม่มีการตัดรายการซ้ำ
มีเพียง event กลุ่ม report.* เท่านั้นที่มีกุญแจการนำส่งแบบคงที่ซึ่งกันการส่งซ้ำได้
webhook ของรายการธุรกรรมจึงอาจมาถึงมากกว่าหนึ่งครั้งสำหรับรายการและสถานะเดียวกัน
เช่นหลังการส่งซ้ำภายในระบบ การส่งซ้ำด้วยมือโดยฝ่ายสนับสนุน หรือการเล่นข้อความซ้ำ
ให้ใช้ id + status (หรือ X-Transaction-ID + X-Webhook-Event) เป็นกุญแจของตัวรับ
และทำให้การรับซ้ำไม่เกิดผลใด ๆ อย่าถือว่า success ครั้งที่สองของ id เดิมคือการชำระเงินครั้งที่สอง
อนึ่ง ค่า timestamp ใน body ถูกสร้างใหม่ทุกครั้งที่ส่ง จึงใช้เป็นกุญแจกันรายการซ้ำไม่ได้
หน้านี้แปลจากเอกสารต้นฉบับภาษาอังกฤษ ส่วนที่เป็นค่าทางเทคนิค — path, ชื่อฟิลด์, header, รหัสข้อผิดพลาด และชื่อคอลัมน์ในไฟล์ส่งออก — คงไว้เป็นภาษาอังกฤษตามที่ระบบใช้จริง

