Qeet Docs
Platform

Webhooks

HMAC-SHA256 signed events, filterable by type, with exponential-backoff retries and a per-webhook dead-letter queue.

Webhooks let your services react to identity events. A subscription's events list is also its opt-in filter — subscribe to exactly the event types you want (or leave it empty to receive everything). Qeet ID signs each payload with HMAC-SHA256, retries with exponential backoff (up to 60 attempts), and parks permanent failures in a dead-letter state you can inspect and retry per delivery.

Register a webhook

POST/v1/webhooksCreate a webhook
GET/v1/tenants/{tenantID}/webhooksList webhooks
POST/v1/webhooks/{id}/testSend a test event
DELETE/v1/webhooks/{id}Delete a webhook
request
JSON
{
  "tenant_id": "…",
  "url": "https://your-app.example/hooks/qeetid",
  "events": ["session.revoked", "token.claims_change"]
}

Some event types worth knowing about

Beyond per-domain events (agent lifecycle, OIDC client changes, …), two are purpose-built for real-time revocation — see Sessions → Real-time revocation signals.

Verify 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.

route handler
TypeScript
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, up to maxDeliveryAttempts (60) tries. A delivery that exhausts every retry is marked dead — inspect and manually retry it per webhook:

GET/v1/webhooks/{id}/deliveriesList a webhook's deliveries (incl. dead ones)
POST/v1/webhooks/{id}/deliveries/{deliveryID}/retryRetry a dead delivery

On this page