Webhooks notify your server when a session reaches a decision.
link Add an endpoint
Create it in the dashboard
Open Developers → Webhooks, enter your HTTPS URL and choose which events it should receive.
Save the signing secret
We show the endpoint's signing secret once. Store it with your other secrets: you'll use it to check that each request really came from us.
Answer quickly
Reply with any 2xx status within a few seconds, then do the work. If you don't, we try again later.
link Events
| Event | Sent when |
|---|---|
session.completed | A session ends with clear or rejected. |
session.review_required | A session ends with consider and needs a person to look. |
session.error | The checks failed for a technical reason after every retry. The session's status is error. |
See Webhook events for the details.
link Check the signature
Every request carries X-Signature: t=<unix time>,v1=<hex>. v1 is HMAC-SHA256, keyed with the endpoint's signing secret, of the time, a full stop and the raw request body. Compute it over the body exactly as received, compare in constant time, and reject times more than five minutes old.
import hashlib, hmac, time
def verify(body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(item.split("=", 1) for item in header.split(","))
signed = f"{parts['t']}.".encode() + body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) <= tolerance
return fresh and hmac.compare_digest(expected, parts["v1"])
import crypto from "node:crypto";
export function verify(body, header, secret, tolerance = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret)
.update(`${parts.t}.`).update(body).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= tolerance;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
Other headers: X-Webhook-Id (the delivery's id, also in the body) and X-Webhook-Event.
link Retries
Anything but a 2xx within 10 seconds is a failure. We try again 1 and 5 minutes, 30 minutes, then 2, 6, 12 and 24 hours after the first attempt, then stop. Redirects aren't followed. Developers → Webhooks lists recent deliveries, resends one, and sends a webhook.test event.
link Handling events well
- Treat the event as a nudge. Read the session from the API before acting on it, so you always act on its current state.
- Expect repeats. The same event can arrive more than once. Use the session
idto make handling it twice harmless. - Don't rely on order. Events can arrive out of order. The session's
statusis what counts.