ต่อระบบ
Webhooks และ event
ชนิดของ event, payload, การตรวจลายเซ็น และตารางลองใหม่
เมื่อมีอะไรเปลี่ยนใน tenant — สมาชิกเข้าใหม่ binding ของ role ย้าย session ถูกถอน — Pi Auth บันทึกเป็น event แล้วส่งไปทุก webhook endpoint ที่ tenant ลงทะเบียนไว้ แอปใช้สิ่งนี้เพื่อให้ cache ทันสมัย สำเนาผังองค์กร หรือบังคับออกจากระบบ
ชนิดของ event
| ชนิด | เกิดเมื่อ |
|---|---|
user.updated | โปรไฟล์ของสมาชิกเปลี่ยน (ชื่อ รูป ภาษา อีเมล) — ส่งให้ทุกองค์กรที่คนนั้นเป็นสมาชิก พร้อม `changes` บอกฟิลด์ที่เปลี่ยน; ไม่มี user.created ต่อองค์กร (ใช้ membership.created) และยังไม่มีการลบบัญชี |
membership.created / membership.updated / membership.left | มีคนเข้า tenant เปลี่ยน role/สถานะ/ตำแหน่ง/หน่วยงาน หรือออก |
org_unit.changed / position.changed / group.changed | ผังองค์กร ตำแหน่ง หรือกลุ่มเปลี่ยน |
role.binding.changed / role.changed | ผูก/ถอด role หรือ permission ของ role เปลี่ยน (สร้าง/แก้/ลบ) — สิทธิ์ที่มีผลอาจต่างไป ให้ล้าง cache |
session.revoked | session ถูกถอนโดยสมาชิกหรือผู้ดูแล ให้ทิ้ง session ที่ตรงกันในแอป |
application.installed / application.uninstalled | tenant ติดตั้งหรือถอนแอป |
app_access.requested / app_access.approved / app_access.rejected | แอปแบบ “รออนุมัติ”: สมาชิกขอเข้าใช้ / ผู้ดูแลอนุมัติ / ปฏิเสธ (data: userId, email, clientId) — อนุมัติแล้วแอปควรให้ผู้ใช้ล็อกอินใหม่ |
webhook.test | ส่งจากปุ่ม “ทดสอบ” ในคอนโซล |
Payload
X-Pi-Event-Id: evt_01J…
X-Pi-Event-Type: membership.updated
X-Pi-Signature: t=1757900000,v1=6f2a…c9
{
"id": "evt_01J…",
"type": "membership.updated",
"version": 1,
"tenantId": "3770ea5f…",
"occurredAt": "2026-09-15T04:12:09.000Z",
"data": { "membershipId": "68dc9b7a…", "changes": ["positionId"] }
}version คือเวอร์ชันโครงของ data ของชนิดนั้น เพิ่มเฉพาะเมื่อเปลี่ยนแบบไม่เข้ากันเดิม ฟิลด์ที่ไม่รู้จักให้ถือว่าเป็นของเพิ่ม
ตรวจลายเซ็น
ทุก delivery มีลายเซ็น 2 แบบ เลือกใช้แบบใดแบบหนึ่ง:
1. JWS — ไม่ต้องมี secret (แนะนำ)
header X-Pi-Signature-Jws เป็น JWT (ES256) ที่ Pi Auth เซ็นด้วย key ชุดเดียวกับ token — แอปตรวจด้วย JWKS ของ issuer ที่มีอยู่แล้ว (PI_AUTH_ISSUER) จึง ไม่ต้องตั้ง env เพิ่มต่อ endpoint และหมุน key ตาม JWKS ให้เอง claims: jti = event id, aud = URL ของ endpoint, evt = type, body_sha256 = base64url(sha256(raw body)), อายุ 5 นาที
import { parsePiWebhook } from "@suphark/auth-client/webhooks";
export async function POST(request: Request) {
const event = await parsePiWebhook(request); // ใช้ PI_AUTH_ISSUER + JWKS
// event.id คงที่ทุกครั้งที่ส่งซ้ำ — ทำ handler ให้ idempotent
await handle(event);
return new Response(null, { status: 204 });
}2. HMAC ด้วย secret ของ endpoint
สำหรับ receiver ที่ไม่ใช้ SDK/ไม่ดึง JWKS: header X-Pi-Signature = t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")> ด้วย secret whsec_… ที่แสดงครั้งเดียวตอนสร้าง endpoint ปฏิเสธ request ที่เก่ากว่า 5 นาที เทียบแบบ constant time (SDK: parsePiWebhook(request, { secret }))
expected = hex(hmac_sha256(secret, f"{t}.{raw_body}"))
ok = constant_time_equal(expected, v1) and abs(now() - int(t)) <= 300การส่งและการลองใหม่
- ตอบ 2xx = สำเร็จ อื่น ๆ จะถูกส่งซ้ำ ยกเว้น 4xx (นอกจาก 408/425/429) ที่ถือว่าล้มเหลวถาวร
- ลองใหม่ตามลำดับ 1 นาที → 5 นาที → 30 นาที → 2 ชม. → 6 ชม. → 15 ชม.: 7 ครั้งในราวหนึ่งวัน แล้วจึงถูกทำเครื่องหมาย dead
- รายการที่ dead ส่งซ้ำได้จาก ตั้งค่า → Webhooks ซึ่งแสดง status code ของแต่ละครั้งด้วย
- event ของ endpoint หนึ่งส่งตามลำดับที่เกิด แต่เมื่อมีการลองใหม่อาจถึงสลับลำดับ — ใช้
occurredAtถ้าลำดับสำคัญ
ลงทะเบียน endpoint
ผู้ดูแล tenant เพิ่ม endpoint ที่ ตั้งค่า → Webhooks: URL, คำอธิบาย (ไม่บังคับ) และชนิด event ที่ต้องการ (ว่าง = ทั้งหมด) แก้ URL/คำอธิบาย/event ทีหลังได้จากเมนูของ endpoint secret ลงนาม (ใช้เฉพาะแบบ HMAC) แสดงครั้งเดียวตอนสร้าง หมุนด้วยการสร้าง endpoint ใหม่แล้วลบอันเก่า ปุ่ม "ส่งเหตุการณ์ทดสอบ" ส่ง webhook.test ถึง endpoint นั้นเสมอไม่ว่าจะ subscribe อะไร