การดูแลระบบ › Webhooks
เริ่มต้น
เนื้อหา
Plugins
การดูแลระบบ
การ Deploy
สูตรสำเร็จ
Webhooks
หน้านี้สอนอะไร
แจ้งบริการอื่นเมื่อเนื้อหาเปลี่ยน พร้อมลายเซ็นและการส่งซ้ำที่ไม่หายแม้ server รีสตาร์ต
ควรอ่านก่อน: Hooks
แจ้งบริการอื่นเมื่อเนื้อหาเปลี่ยนแปลง เช่น build static site ใหม่ ล้าง cache ของ CDN หรือแจ้งเตือนไปยัง ช่องแชต
export default defineConfig({
webhooks: [
// Every event of every collection and global, signed.
{ url: 'https://example.com/cms-hook', secret: process.env.WEBHOOK_SECRET },
// Only publishing of posts, e.g. a Vercel or Netlify build hook.
{ url: process.env.BUILD_HOOK_URL!, events: ['publish', 'unpublish'], collections: ['posts'], globals: [] },
],
})| ตัวเลือก | ค่าเริ่มต้น | |
|---|---|---|
url | จำเป็น | ปลายทางที่ event จะถูก POST ไป |
events | ทั้งหมด | create, update, delete, publish, unpublish, draft |
collections | ทั้งหมดยกเว้น users | slug ของ collection ใช้ [] เพื่อไม่เลือกเลย บัญชีผู้ใช้จะถูกส่งเฉพาะเมื่อระบุ users ไว้ |
globals | ทั้งหมด | slug ของ global ใช้ [] เพื่อไม่เลือกเลย |
secret | — | ลงลายเซ็นทุก request (ดูด้านล่าง) |
headers | — | header เพิ่มเติม เช่น token ที่ฝั่งรับใช้ตรวจสอบ |
Events
| Event | เมื่อใด |
|---|---|
create | มีการสร้างเอกสาร |
update | เอกสารที่จัดเก็บไว้หรือ global มีการเปลี่ยนแปลง |
delete | มีการลบเอกสาร |
publish | status เปลี่ยนเป็น published (ส่งพร้อมกับ create หรือ update) |
unpublish | status ไม่เป็น published อีกต่อไป |
draft | บันทึกเฉพาะฉบับร่าง เนื้อหาที่แสดงบนเว็บไม่เปลี่ยน (เวอร์ชัน) |
การเผยแพร่ตามเวลาที่ตั้งไว้จะส่ง event เดียวกัน
Event ของคุณเอง
plugin และแอปส่ง event ของตัวเองได้ เช่น order.paid ของร้านค้า ประกาศชื่อไว้ใน events
(plugin ประกาศให้เอง) ส่งด้วย cms.emit() แล้วระบุใน events ของ webhook event เหล่านี้ส่งไปเฉพาะ webhook ที่ระบุไว้
export default defineConfig({
events: ['invoice.sent'],
webhooks: [{ url: 'https://erp.example.com/hook', events: ['invoice.sent'], secret }],
})
cms.emit('invoice.sent', { number: 'INV-1' }, { collection: 'invoices', id: 7 })body คือ { event, collection?, id?, doc, timestamp } โดย doc คือสิ่งที่ส่งให้ emit การส่ง ลายเซ็น และการส่งซ้ำเหมือนเดิม
Requests
POST /cms-hook
content-type: application/json
x-easy-cms-event: publish
x-easy-cms-delivery: 2f1c… (the same on retries)
x-easy-cms-timestamp: 1761700000 (with `secret`: this attempt's time, Unix seconds)
x-easy-cms-signature-v2: v1=5c3e… (with `secret`)
x-easy-cms-signature: sha256=9a0b… (with `secret`, deprecated)
{ "event": "publish", "collection": "posts", "id": 12, "doc": { … }, "timestamp": "…" }doc คือเอกสารที่จัดเก็บไว้ ครบทุกภาษา โดยไม่มี field ที่ซ่อนอยู่
ตรวจลายเซ็น
เมื่อตั้ง secret การส่งแต่ละครั้งจะเซ็นด้วย HMAC-SHA256 ของ <delivery>.<timestamp>.<body> โดย body
เป็นแบบดิบตามที่ส่ง timestamp เป็นเวลาของการส่งครั้งนั้น ใครที่ดักจับ request ได้จึงเอาไปส่งซ้ำทีหลังไม่ได้
ใน Node ใช้ verifyWebhook() จาก @easy-cms/core ตรวจและได้ payload กลับมา
import { verifyWebhook } from '@easy-cms/core'
export async function POST(request: Request) {
const body = await request.text() // body ดิบ ก่อน parse
let payload
try {
payload = verifyWebhook(body, request.headers, process.env.CMS_WEBHOOK_SECRET!)
} catch {
return new Response('Bad signature', { status: 401 })
}
// …
}- ห้านาที ปฏิเสธ timestamp ที่ห่างจากตอนนี้เกิน 5 นาที (เปลี่ยนได้ด้วย
{ toleranceSeconds }) นาฬิกาของปลายทางจึงต้องตรง - ข้ามรายการที่จัดการแล้ว ภายในห้านาทีนั้น request ที่ส่งซ้ำยังผ่านการตรวจ ให้เก็บ
x-easy-cms-deliveryที่จัดการแล้วไว้สักพักและข้ามรายการซ้ำ การลองใหม่ใช้ id เดิม - หลาย secret ส่งเป็น array ระหว่างเปลี่ยน secret ตัวไหนตรงก็ผ่าน
ภาษาอื่น ให้เทียบแบบใช้เวลาคงที่
import { createHmac, timingSafeEqual } from 'node:crypto'
const signed = `${headers['x-easy-cms-delivery']}.${headers['x-easy-cms-timestamp']}.${rawBody}`
const expected = `v1=${createHmac('sha256', secret).update(signed).digest('hex')}`
const given = headers['x-easy-cms-signature-v2']
const valid =
expected.length === given.length && timingSafeEqual(Buffer.from(expected), Buffer.from(given))
// และ |ตอนนี้ − x-easy-cms-timestamp| ≤ 300 วินาทีx-easy-cms-signature (HMAC ของ body อย่างเดียว) ยังส่งอยู่จนถึง 2.0 แต่ request ที่ถูกดักจับเอาไปส่งซ้ำได้
ให้เปลี่ยนไปใช้ x-easy-cms-signature-v2
Delivery
webhook จะถูกส่งหลังจากบันทึกการเปลี่ยนแปลงแล้ว และจะไม่ทำให้การบันทึกช้าลงหรือล้มเหลว กรณี timeout
(10 วินาที), ข้อผิดพลาดของเครือข่าย, คำตอบ 429 และ 5xx จะลองส่งใหม่ ส่วนคำตอบ 4xx อื่นๆ
จะถูกบันทึก log และไม่ลองใหม่
- ลองใหม่เร็วๆ สองครั้งใน process ที่ทำการเปลี่ยนแปลง (หลัง 1 วินาทีและ 5 วินาที)
- การส่งแต่ละครั้งจะถูกบันทึกลงฐานข้อมูล (ตารางภายใน
webhook-deliveries) ก่อนส่งครั้งแรก และลบออกเมื่อสำเร็จ จึงไม่หายแม้ restart หรือฟังก์ชัน serverless หยุดทำงานกลางคันระหว่างส่ง ถ้า process หยุดระหว่างส่ง การรันรอบถัดไปจะส่งต่อให้หลังผ่านไป 5 นาที - หลังจากนั้นจะลองใหม่พร้อมกับงานที่ตั้งเวลาไว้: หลัง 1 นาที, 5 นาที,
30 นาที, 2, 6 และ 12 ชั่วโมง server จะรันทุกนาทีเอง ส่วน serverless ให้ cron เรียก
<api>/jobs/run - หลังครั้งสุดท้าย (ประมาณหนึ่งวัน) การส่งจะถูกเก็บไว้เป็น
state: 'failed'พร้อม error ล่าสุด และไม่ลองใหม่อีก admin ดูได้ที่ ตั้งค่า → การส่ง และกดส่งซ้ำหรือลบได้ (ตรวจสุขภาพระบบ) รายการที่ล้มจะถูกลบเมื่อเกิน 30 วัน
ทุกครั้งที่ลองใหม่จะส่ง body และ x-easy-cms-delivery เหมือนเดิม (timestamp และลายเซ็นใหม่) ปลายทางจึงข้ามการส่งที่เคยจัดการแล้วได้
การเพิ่ม webhooks ใน config จะเพิ่มตาราง webhook-deliveries ให้สร้าง migration เหมือนการเปลี่ยน config ทั่วไป
บนแพลตฟอร์ม serverless ฟังก์ชันอาจหยุดทำงานก่อนที่ webhook จะถูกส่ง: ให้เรียก
await cms.flushWebhooks() ก่อน return เมื่อเขียนข้อมูลผ่าน Local API บนแพลตฟอร์มเหล่านั้น
ส่วน await cms.retryWebhooks() จะลองส่งรายการที่บันทึกไว้ซึ่งถึงเวลาแล้ว (runJobs() ทำทั้งส่วนนี้และการเผยแพร่ตามเวลา)
ขั้นต่อไป
- ฉบับร่าง เวอร์ชัน และการตั้งเวลา: การเผยแพร่ทำให้เกิดอะไร
- CLI: ส่งซ้ำจาก cron