Skip to content

บันทึกและ audit log ​

หน้านี้คือที่ที่คุณมาตอบคำถาม 3 ข้อ

  1. เรายิงอะไรไปแล้วได้อะไรกลับมา
  2. webhook ใบนั้นส่งถึงเราหรือยัง ล้มที่ attempt ไหน เพราะอะไร
  3. ใครในทีมกดอะไรในระบบหลังบ้าน

1. request / response ย้อนหลัง ​

http
GET /v1/bo/logs

ทุก response ของเรามี request_id — ทั้งตอนสำเร็จและตอน error

json
{ "error": {
    "code": "signature_invalid",
    "type": "authentication_error",
    "message": "Signature does not match the canonical string.",
    "message_th": "ลายเซ็นไม่ตรงกับ canonical string",
    "retryable": false,
    "retry_after_seconds": null,
    "request_id": "req_01JQA5T8V0X2Z4B6D8F0H2J4L6",
    "details": {} } }

เก็บ request_id ทุกครั้งที่ยิง ไม่ใช่เฉพาะตอน error

เวลาส่งเคสให้ทีมงาน request_id ตัวเดียวประหยัดเวลาไปหนึ่งวัน เคสที่ส่งมาว่า "สร้างรายการไม่ได้เมื่อบ่ายวานนี้" โดยไม่มี request_id และไม่มี deposit_id ไล่หาแทบไม่ได้เลย

สิ่งที่ห้ามอยู่ใน log ฝั่งคุณ

  • qr_payload — ข้างในมีข้อมูลบัญชีของผู้ถอน ผูกไว้ในเงื่อนไขการใช้งานว่า ห้าม log ห้ามเก็บลงฐานข้อมูล ห้ามส่งต่อ ใช้แสดงผลแล้วทิ้ง
  • เลขบัญชีปลายทางแบบเต็ม
  • sk_live_* และค่า PG-Signature

qr_payload มีอายุเท่ากับ expires_at ซึ่งบางเส้นทางสั้นถึง ~4 นาที ⇒ log ไว้ก็ใช้อะไรไม่ได้อยู่ดี มีแต่ความเสี่ยง

2. สถานะการส่ง webhook ทุกใบ ​

bash
curl -sS "https://api.example-wl.com/v1/bo/webhooks/whep_01K2A9CDEF3G/deliveries?limit=50&status=failed" \
  -H "Authorization: Bearer $BO_OIDC_ACCESS_TOKEN"
json
{
  "data": [
    {
      "id": "whd_01K3M8QX4C7RZ2XT5NBVKD9H0P",
      "object": "webhook_delivery",
      "endpoint_id": "whep_01K2A9CDEF3G",
      "event_id": "evt_01K3M8QW7Z4N2VYB6H0RSTA9CD",
      "event_type": "deposit.settled",
      "status": "failed",
      "attempt": 11,
      "resend": false,
      "created_at": "2026-08-21T04:12:33.940Z",
      "last_attempt_at": "2026-08-25T00:49:07.552Z",
      "next_retry_at": null,
      "response_status": 504,
      "response_body_excerpt": "upstream request timeout",
      "duration_ms": 15002,
      "error": "response_timeout"
    },
    {
      "id": "whd_01K3M9F3AB8SY4WT6PCXLE1J2Q",
      "object": "webhook_delivery",
      "endpoint_id": "whep_01K2A9CDEF3G",
      "event_id": "evt_01K3M9F2XN8VQKR4T0BDHY7WZE",
      "event_type": "deposit.under_review",
      "status": "delivered",
      "attempt": 2,
      "resend": false,
      "created_at": "2026-08-21T04:19:08.810Z",
      "last_attempt_at": "2026-08-21T04:19:19.043Z",
      "next_retry_at": null,
      "response_status": 200,
      "response_body_excerpt": "ok",
      "duration_ms": 214,
      "error": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}

สถานะของ delivery มี 4 ค่า ไม่มีค่าอื่น ​

statusแปลว่า
pendingยังอยู่ในรอบ retry · ดูเวลาถัดไปที่ next_retry_at
deliveredได้ 2xx แล้ว
failedครบ 11 attempt แล้วยังไม่ได้ 2xx · event ยังอยู่ 30 วัน กดส่งซ้ำได้
canceledendpoint ถูกลบ/ปิด ระหว่างที่ delivery ยังค้าง

อะไรนับว่าสำเร็จ ​

เกณฑ์ค่า
สำเร็จ200 201 202 204 เท่านั้น
timeout เชื่อมต่อ5 วินาที
timeout ถึง byte แรกของ response10 วินาที
timeout รวมทั้ง request15 วินาที (แนะนำให้ตอบภายใน 3 วินาที)
body ที่เราอ่านเก็บไว้สูงสุด 8 KB · เนื้อหาไม่มีผลต่อความสำเร็จ
ขนาด body ที่เราส่งไม่เกิน 256 KB (รับประกัน)
3xx🔴 ไม่ตาม redirect = ล้มเหลว
410 Gone🔴 ล้มเหลวถาวร หยุด retry ทันที + ตั้งธง endpoint หายไป
429ล้มเหลว → retry ตามตาราง (เรา ไม่อ่าน Retry-After ของคุณ ตารางคงที่เพื่อให้คาดเดาได้)
4xx/5xx อื่น · TLS/DNS/connection errorล้มเหลว → retry ตามตาราง

ตอบ 2xx ก่อน แล้วค่อยทำงาน

งานหนักใน handler ⇒ ตอบช้าเกิน 15 วินาที ⇒ เรานับว่าล้มเหลว ⇒ retry ⇒ ประมวลผลซ้ำ ⇒ เครดิตซ้ำ รูปแบบที่ถูก: verify ลายเซ็น → เขียนลงคิว → ตอบ 200 → ทำงานจริงแบบ async

ตารางรอบ retry ​

attemptหน่วงจากครั้งก่อนสะสมจากที่สร้าง event
1ทันที00:00:00
210 วินาที00:00:10
31 นาที00:01:10
45 นาที00:06:10
530 นาที00:36:10
62 ชั่วโมง02:36:10
76 ชั่วโมง08:36:10
812 ชั่วโมง20:36:10
924 ชั่วโมง44:36:10
1024 ชั่วโมง68:36:10
1124 ชั่วโมง92:36:10

รวม 11 ครั้ง หน้าต่างประมาณ 92 ชั่วโมง 36 นาที (3 วัน 20 ชั่วโมง) · มี jitter ±10% ต่อรอบ ⇒ ค่าจริงดูที่ next_retry_at

หน้าต่าง retry สั้นกว่าอายุ Idempotency-Key โดยตั้งใจ

retry รอบสุดท้าย (~92 ชั่วโมง) ยังชนกับ idempotency record ของคุณที่มีอายุ 7 วันได้เสมอ ⇒ 🔴 TTL ของ idempotency ฝั่งคุณ ห้ามสั้นกว่า 7 วัน ไม่งั้นรายการซ้ำจะโผล่มาจาก retry รอบท้าย ๆ

เลิก retry เมื่อไร ​

  • ได้ 2xx
  • ครบ 11 attempt → failed (event ยังอยู่ 30 วัน กดส่งซ้ำได้)
  • ได้ 410
  • คุณลบหรือปิด endpoint (delivery ที่ค้างกลายเป็น canceled)
  • ระบบปิด endpoint ให้อัตโนมัติ

3. กดส่งซ้ำ ​

ส่งซ้ำใบเดียว ​

bash
curl -sS -X POST "https://api.example-wl.com/v1/bo/webhooks/whep_01K2A9CDEF3G/deliveries/whd_01K3M8QX4C7RZ2XT5NBVKD9H0P/resend" \
  -H "Authorization: Bearer $BO_OIDC_ACCESS_TOKEN" \
  -H "X-PG-Step-Up-Token: $BO_STEP_UP_TOKEN" \
  -H "Idempotency-Key: resend-whd01K3M8QX4C7RZ2XT5NBVKD9H0P-01" \
  -H "Content-Type: application/json"
json
{
  "delivery_id": "whd_01K3V2R8T5WYZ7XB1KDMNQH4PS",
  "event_id": "evt_01K3M8QW7Z4N2VYB6H0RSTA9CD",
  "status": "pending",
  "attempt": 1,
  "resend": true,
  "created_at": "2026-08-23T05:00:00.412Z"
}
เรื่องสิ่งที่เกิด
event.id🔴 เหมือนเดิมทุกประการ ⇒ การกันซ้ำฝั่งคุณยังทำงาน กดส่งซ้ำจึงปลอดภัยเสมอ
ไบต์ของ bodyเหมือนเดิมทุกไบต์ — payload ถูกเก็บไว้ตั้งแต่ตอนสร้าง ไม่ render ใหม่ ⇒ ไม่มีปัญหาเวอร์ชัน API เปลี่ยนกลางทาง
ลายเซ็นคำนวณใหม่ (เพราะ t ต้องใหม่) เซ็นด้วยกุญแจที่ active ตอนนี้
delivery metadataอยู่ใน header เท่านั้น: X-PG-Delivery-Id ใบใหม่ · X-PG-Attempt: 1 · X-PG-Resend: manual
รอบ retryได้ครบ 11 รอบใหม่
อายุส่งซ้ำได้ภายใน 30 วัน นับจาก created_at ของ event

กดส่งซ้ำแล้วเครดิตซ้ำไหม — ไม่ ถ้าคุณทำ 2 อย่างนี้ครบ

  1. transactional inbox มี UNIQUE(event_id) เก็บอย่างน้อย 31 วัน · เจอซ้ำ ตอบ 200 อย่าตอบ error
  2. version guard เป็น UPDATE ... WHERE version < $new แล้วเช็คจำนวนแถวที่ถูกแก้ · ได้ 0 แถว = event เก่า ⇒ ตอบ 200 แล้วไม่ทำอะไร

ระบบรับประกันว่า ไม่มีสอง event ของ object เดียวกันที่ version เท่ากัน ⇒ รูปแบบนี้ปลอดภัยจริง

ส่งซ้ำทีละมาก ​

bash
curl -sS -X POST "https://api.example-wl.com/v1/bo/webhooks/whep_01K2A9CDEF3G/replay" \
  -H "Authorization: Bearer $BO_OIDC_ACCESS_TOKEN" \
  -H "X-PG-Step-Up-Token: $BO_STEP_UP_TOKEN" \
  -H "Idempotency-Key: replay-whep01K2A9CDEF3G-20260823-01" \
  -H "Content-Type: application/json" \
  -d '{
    "event_types": ["deposit.settled", "deposit.reversed"],
    "created_after": "2026-08-18T00:00:00.000Z",
    "created_before": "2026-08-21T00:00:00.000Z",
    "only_failed": true
  }'

ทุก /v1/bo/* ใช้ OIDC access token ของเจ้าหน้าที่ ไม่ใช้ sk_live_* หรือ PG HMAC การอ่าน delivery ใช้ token อย่างเดียว ส่วน resend/replay เป็น privileged action จึงต้องมี step-up token ที่ยังไม่หมดอายุด้วย

  • สูงสุด 10,000 event ต่อคำสั่ง ปล่อยที่ 50 ใบ/วินาที
  • มีบันทึก audit ทุกครั้ง
  • 🔴 คำสั่งแบบนี้ ไม่นับ ในโควตา webhook_resend (โควตานั้นนับ "คำสั่ง" ที่ 10 ครั้ง/นาที ต่อ endpoint)

ก่อนกด replay ให้ยืนยันว่า handler ของคุณกันซ้ำได้จริงก่อน

replay 10,000 ใบเข้า handler ที่ไม่มี UNIQUE(event_id) = เครดิตซ้ำ 10,000 ครั้ง ทดสอบด้วยการ resend ใบเดียวก่อน แล้วดูว่ายอดในระบบคุณ ไม่ขยับ จึงค่อยยิงทั้งชุด

endpoint ล่มยาว ​

ล่มติดกัน 6 ชั่วโมง    → อีเมลเตือน + แถบแจ้งเตือน (ยัง retry ปกติ)
ล่มติดกัน 12 ชั่วโมง   → อีเมลเตือนครั้งที่ 2 + แจ้งผู้ให้บริการชั้นบน
ล่มติดกัน 24 ชั่วโมง   → 🔴 ระบบปิด endpoint ให้อัตโนมัติ
    เงื่อนไข: 24 ชั่วโมงล่าสุดสำเร็จ 0 ครั้ง **และ** delivery ที่ล้มเหลว ≥ 50 ใบ
    ผล: หยุดสร้าง delivery ใหม่ (event ยังเก็บครบ 30 วัน)
        · delivery ที่ค้างกลายเป็น canceled
        · ยิง webhook.endpoint.disabled ไป endpoint กลุ่มอื่น + อีเมล + บันทึก audit
  • เปิดคืนต้องกดเอง จากระบบหลังบ้าน — มีปุ่มส่งซ้ำ delivery ที่ล้มเหลวใน 72 ชั่วโมงล่าสุดให้
  • endpoint ที่มี success อย่างน้อย 1 ครั้งใน 24 ชั่วโมง จะไม่ถูกปิด ⇒ endpoint ที่ล้ม 90% ยังอยู่ต่อได้ แต่จะโดนเตือน
  • 🔴 ถ้าคุณมี endpoint เดียวและมันถูกปิด event ที่ปิดไม่ได้จะถูกส่งทางอีเมล ไปยังผู้ติดต่อทางเทคนิค

นี่คือเหตุผลที่ผู้ติดต่อทางเทคนิคต้องเป็นอีเมลที่มีคนอ่าน

ไม่ใช่ noreply@ และไม่ใช่กล่องที่ไม่มีใครเปิด วันที่ระบบส่งอีเมลว่า endpoint ถูกปิด คือวันที่คุณกำลังจะไม่รู้เรื่องการกลับรายการที่เกิดขึ้นกับกระเป๋าตัวเอง

header ที่ติดมากับทุก delivery ​

http
POST /webhooks/pg HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: PG-Webhooks/1.0
X-PG-Signature: v1,kid=whk_2026a,t=1755749553,s=3rPYqVQ
X-PG-Event-Id: evt_01K3M8QW7Z4N2VYB6H0RSTA9CD
X-PG-Event-Type: deposit.settled
X-PG-Delivery-Id: whd_01K3M8QX4C7RZ2XT5NBVKD9H0P
X-PG-Attempt: 3
X-PG-Api-Version: 2026-08-01
X-PG-Livemode: true

delivery headers ใช้เพื่อ observability เท่านั้น

verify ลายเซ็นบน raw bytes ก่อน แตะ body · business event อยู่ใน body; delivery headers ไม่ได้อยู่ใน canonical signature จึงห้ามใช้ตัดสินเรื่องเงิน สิทธิ์ หรือ state X-PG-Signature ต้อง parse แบบแยก key=value ห้าม parse ด้วยตำแหน่ง เพราะเราอาจเพิ่ม field ในอนาคต

สิ่งที่ควร log ฝั่งคุณทุกครั้ง: event.id · X-PG-Delivery-Id · X-PG-Attempt · ผลของ version guard (กี่แถวถูกแก้) · เวลาที่ใช้ใน handler

ตามหา event ที่หายไป ​

พบช่องว่างของ sequence จาก สคริปต์กระทบยอด แล้วทำตามลำดับนี้

  1. ช่องว่างที่เกิดภายใน 4 วันล่าสุด = ปกติ event ยังอยู่ในรอบ retry
  2. ดึง event ตรง ๆ ด้วย GET /v1/pay/events?since_sequence= — ถ้าดึงได้แปลว่าไม่ได้หาย แค่ยังส่งไม่ถึง
  3. ดู GET /v1/bo/webhooks/{endpoint_id}/deliveries ว่าใบนั้น status เป็นอะไร ล้มที่ response_status ไหน
  4. แก้ที่ต้นเหตุก่อน (endpoint ตอบ 3xx · TLS หมดอายุ · handler timeout) แล้วค่อย กดส่งซ้ำ
  5. ช่องว่างที่ค้างเกิน 4 วันและดึงจาก /v1/pay/events ไม่ได้ = หายจริง ⇒ ส่งเรื่องให้ทีมงานพร้อมช่วง sequence

4. ใครกดอะไรในระบบหลังบ้าน ​

http
GET /v1/bo/reports/action-logs

ทุกการกระทำที่มีผลกับเงินหรือกับการตั้งค่าถูกบันทึกไว้ — การอนุมัติและปฏิเสธรายการถอน · การต่อเวลา · การเปิดเรื่องร้องเรียน · การกดส่งซ้ำ webhook · การแก้ค่าตั้งค่า

สิ่งที่ audit log ฝั่งเรา ไม่มี ให้

บันทึกภายในของเจ้าหน้าที่ที่ตัดสินเรื่องในคิวตรวจสอบ ชื่อเจ้าหน้าที่ และเวลาที่ผูกพันภายใน ไม่ถูกส่งออก ทุกช่องทาง สิ่งที่คุณเห็นเกี่ยวกับคิวตรวจสอบมี 3 อย่าง: review.escalated · review.four_eyes_required · review.estimated_resolution_bucket

เช็กลิสต์งาน log ​

  • [ ] log event.id + X-PG-Delivery-Id + X-PG-Attempt ทุกครั้งที่รับ webhook
  • [ ] transactional inbox มี UNIQUE(event_id) เก็บอย่างน้อย 31 วัน · ซ้ำ = ตอบ 200
  • [ ] เก็บ request_id ของทุก request ที่ยิงออก ไม่ใช่เฉพาะตอน error
  • [ ] ไม่ log qr_payload ไม่ log เลขบัญชีปลายทางแบบเต็ม
  • [ ] alert เมื่อ handler ตอบไม่ใช่ 2xx เกิน 1% ใน 5 นาที
  • [ ] alert เมื่อตรวจลายเซ็นไม่ผ่านแม้แต่ครั้งเดียว
  • [ ] ทดสอบ resend ใบเดียวแล้วยืนยันว่ายอดไม่ขยับ ก่อนใช้ replay ทีละมาก
  • [ ] ผู้ติดต่อทางเทคนิคเป็นอีเมลที่มีคนอ่านจริง

ขั้นต่อไป ​

เอกสารชุดนี้อิงสัญญา API เวอร์ชัน 2026-08-01