PayMeNow

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:

json
{
  "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

ฟิลด์ชนิดคำอธิบาย
idstringรหัสรายการ
typestringdeposit หรือ withdraw
amountnumberจำนวนเงินที่ขอไว้ตอนแรก
paidAmountnumberจำนวนเงินที่โอนจริง (ยอดสตางค์ไม่ซ้ำที่อยู่ใน QR) เป็น 0 เว้นแต่ status เป็น success
feenumberค่าธรรมเนียมที่เรียกเก็บ
netAmountnumberamount - fee
bankAccountNostringบัญชีผู้ชำระเงิน (รับเงิน) หรือบัญชีปลายทาง (จ่ายเงินออก)
bankAccountNamestringชื่อเจ้าของบัญชี
bankCodestringรหัสธนาคาร
bankNamestringชื่อธนาคารภาษาไทย แปลงจาก bankCode (หากไม่รู้จักจะคืนตัวรหัสกลับมา)
currencystringรหัสสกุลเงิน
referenceNostringค่า reference_id ของคุณ
statusstringsuccess, failed, expired หรือ refunded
timestampstringเวลาของ ความพยายามส่งครั้งนี้ (RFC 3339 เวลาไทย) เปลี่ยนทุกครั้งที่ส่งซ้ำ
paidAtstringเวลาที่ชำระเงินสำเร็จ (RFC 3339 ระดับนาโนวินาที) เป็นข้อความว่างหากยังไม่ชำระ
failureReasonstringมีเฉพาะเมื่อ status เป็น failed หรือ refunded

#ค่า status ของ webhook และ event ที่คู่กัน

statusX-Webhook-Eventสถานะของรายการ
successtransaction.completedcompleted
failedtransaction.failedfailed
expiredtransaction.expiredexpired
refundedtransaction.refundedrefunded

expired เป็นค่าที่ใช้งานจริง ไม่ใช่ชื่อเก่าที่แทน failed ส่วน webhook แบบ refunded หมายความว่ารายการที่เคยรายงานว่า success ถูกกลับรายการแล้ว ให้ยกเลิกการเข้ายอดของคำสั่งซื้อนั้น โดยค่า failureReason จะบอกเหตุผลจากผู้ดูแลระบบ

#event ของรายงาน (รูปแบบต่างออกไป)

report.daily_summary ซึ่งเป็นสรุปยอดคงเหลือวันละครั้ง ส่งผ่านช่องทางเดียวกัน ใช้การเซ็น และกติกาการส่งซ้ำแบบเดียวกัน แต่ body ของมัน เป็น ซองของ event:

json
{
  "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-Typeapplication/json
X-Webhook-Signatureลายเซ็น HMAC-SHA256 เลขฐานสิบหกตัวพิมพ์เล็ก ดูด้านล่าง
X-Webhook-Timestampเวลา Unix (วินาที) ของความพยายามส่งครั้งนี้
X-Webhook-Eventtransaction.completed / .failed / .expired / .refunded หรือ report.daily_summary
X-Transaction-IDรหัสรายการ หรือสำหรับ event ของรายงานคือกุญแจการนำส่งแบบคงที่

ระบบไม่มี header X-Webhook-ID และไม่ได้ตั้ง User-Agent เฉพาะ

#การตรวจสอบ webhook

text
X-Webhook-Signature = lowercase_hex(HMAC-SHA256(SecretKey, X-Webhook-Timestamp + "|" + raw_body))

กุญแจที่ใช้เซ็นคือ secret key ของ API ของคุณ — ค่า sk_… ตัวเดียวกับที่ใช้เซ็นคำขอ ไม่ใช่ ฟิลด์ secret ที่ endpoint ตั้งค่า webhook ของแดชบอร์ดคืนมา (ค่านั้นไม่ได้ใช้เซ็น)

ให้ตรวจสอบกับ ไบต์ดิบ ที่คุณได้รับ อย่าใช้สำเนาที่แปลงเป็นข้อความใหม่:

python
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, รหัสข้อผิดพลาด และชื่อคอลัมน์ในไฟล์ส่งออก — คงไว้เป็นภาษาอังกฤษตามที่ระบบใช้จริง