Webhooks
HMAC-SHA256 signed events delivered via a transactional outbox, with exponential-backoff retries and a dead-letter queue.
Webhooks let your services react to identity events. Qeet ID delivers them through a transactional outbox (so an event is recorded atomically with the change that produced it), signs each payload with HMAC-SHA256, retries with exponential backoff, and parks permanent failures in a dead-letter queue (DLQ).
Register a webhook
/v1/webhooksCreate a webhook/v1/tenants/{tenantID}/webhooksList webhooks/v1/webhooks/{id}/testSend a test event/v1/webhooks/{id}Delete a webhookVerify the signature
Every delivery carries an HMAC-SHA256 signature over the raw body. Always verify before processing — and verify against the raw bytes, not a re-serialized object.
import crypto from "node:crypto";
export async function POST(req: Request) {
const body = await req.text(); // raw bytes
const sig = req.headers.get("x-qeetid-signature") ?? "";
const expected = crypto
.createHmac("sha256", process.env.QEETID_WEBHOOK_SECRET!)
.update(body)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response("bad signature", { status: 400 });
}
// process the event …
return new Response(null, { status: 204 });
}Make handlers idempotent
Delivery is at-least-once, so the same event may arrive more than once (e.g. after a retry). De-duplicate on the event id.
Retries & the dead-letter queue
If your endpoint returns non-2xx or times out, Qeet ID retries with exponential backoff. Events that exhaust retries land in the DLQ for inspection and replay.
/v1/admin/outbox/dlqInspect the dead-letter queueThe transactional outbox guarantees an event is enqueued in the same transaction as the state change — no "the row updated but the webhook never fired" gaps.