Integrate

Webhooks

Event delivery to your endpoints.

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

EventSent when
session.completedA session ends with clear or rejected.
session.review_requiredA session ends with consider and needs a person to look.
session.errorThe 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.

Python
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"])
Node.js
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 id to make handling it twice harmless.
  • Don't rely on order. Events can arrive out of order. The session's status is what counts.
Was this page helpful?

Authorization

Your secret key stays in this tab: it is sent with this request only and never stored.

Body application/json

Response

Send the request to see the response.