สารบัญ

ต่อระบบ

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.revokedsession ถูกถอนโดยสมาชิกหรือผู้ดูแล ให้ทิ้ง session ที่ตรงกันในแอป
application.installed / application.uninstalledtenant ติดตั้งหรือถอนแอป
app_access.requested / app_access.approved / app_access.rejectedแอปแบบ “รออนุมัติ”: สมาชิกขอเข้าใช้ / ผู้ดูแลอนุมัติ / ปฏิเสธ (data: userId, email, clientId) — อนุมัติแล้วแอปควรให้ผู้ใช้ล็อกอินใหม่
webhook.testส่งจากปุ่ม “ทดสอบ” ในคอนโซล

Payload

POST https://your-app/api/pi-webhook
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 นาที

app/api/pi-webhook/route.ts
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 อะไร