Skip to content

Platform · Webhooks and API

One call to start. One signed event to finish.

Create a session with an API key, send the person the link, and receive `session.status_updated` for every transition, signed with HMAC-SHA256 and retried for about 11 hours. Standalone endpoints run single checks without a hosted flow.

kycverify · productwebhooks and api
delivery attempts over about 10 h 43 min
8
timestamp tolerance for receivers
5 min
standalone check endpoints
5
HMAC-SHA256 · v1

What it checks

Webhooks and API, check by check.

Each rule below is what the engine actually runs.

  • Signed

    x-kyc-signature: v1=<hex HMAC-SHA256(secret, "{timestamp}.{raw_body}")> with x-kyc-timestamp, so receivers can reject replays.

  • Retried

    Any non-2xx or a 10-second timeout schedules a retry after 30 s, 2 m, 10 m, 30 m, 1 h, 3 h and 6 h: eight attempts in all, then the delivery is marked failed. Retries are persisted and survive restarts.

  • Redeliverable

    Every delivery and its response is listed in the console and can be redelivered with one click.

  • Test and live keys

    kyc_test_ and kyc_live_ keys belong to separate apps. Keys are stored as a prefix and a SHA-256 hash and compared in constant time.

Try it

Sign and verify a delivery in your browser.

The calculator uses Web Crypto to compute exactly what KYCVerify sends in x-kyc-signature. The defaults are the Rust test vector, so you can see the two implementations agree.

  • The rules are ported line for line from the Rust engine, and the page names the file.
  • Everything runs locally in this tab. No request is made while you type.
  • Reset puts the example back; nothing is saved.

Webhook signature calculator

Runs in your browser

Shown once in the console when the app is created or the secret is rotated.

Unix seconds when the delivery was signed.

Sign the exact bytes you received. Re-serialising parsed JSON changes whitespace and breaks the signature.

Signed string

1700000000.{"a":1}

x-kyc-signature

computing…

Paste a header value to check it the way a receiver should.

Signature does not match

Reject the delivery with a non-2xx status; KYCVerify will retry it.

Timestamp tolerance

Waiting for the clock

The page reads your clock after it loads.

Logic ported from backend/crates/kyc-api/src/webhooks.rs. Nothing you type leaves this page.

How it works

What happens, in order.

  1. 1

    Create

    POST /v1/sessions returns the session id, the hosted url and its expiry.

  2. 2

    Verify

    The person completes the hosted flow at /verify/{token}.

  3. 3

    Receive

    Your webhook gets the status change, with the full decision once the session is final. GET /v1/sessions/{id}/decision fetches it any time.

Set up per app

API keys and webhook endpoints belong to an app. Admins create them in the console; a key is shown once and kept only as a prefix and a SHA-256 hash, and each endpoint gets its own signing secret.

Reference

The whole public API.

The whole public API.
MethodPathWhat it does
POST/v1/sessionsCreate a session; returns the hosted url
GET/v1/sessionsList sessions, paginated
GET/v1/sessions/{id}One session's summary
GET/v1/sessions/{id}/decisionStatus, identity, every check, the review
PATCH/v1/sessions/{id}/statusApprove or decline a session in review
DELETE/v1/sessions/{id}Purge a session's data now
GET/v1/workflowsThe app's workflows
POST/v1/checks/amlScreen a name
POST/v1/checks/panValidate a PAN
POST/v1/checks/mrzParse an MRZ
POST/v1/checks/face-matchCompare two face images
POST/v1/checks/aadhaar/secure-qrVerify an Aadhaar Secure QR

Delivery and signing

What a receiver sees, and when KYCVerify tries again.

Webhooks and API codes
CodeOutcomeWhen
+30 s, 2 m, 10 m, 30 mretries 1-4After any non-2xx response or a 10-second timeout.
+1 h, 3 h, 6 hretries 5-7Persisted, so they survive a restart. Eight attempts in all over about 10 h 43 min, then the delivery is marked failed.
x-kyc-timestampheaderUnix seconds when the delivery was signed; reject anything older than 5 minutes.
x-kyc-signatureheaderv1= followed by the hex HMAC-SHA256 of "{timestamp}.{raw_body}" under your endpoint secret.

API

Verify every delivery before you trust it.

Recompute the signature over the raw body, compare in constant time, and reject stale timestamps. Then fetch the decision, or use the one in the payload when the session is final.

In a session

Webhook body

Example · json
{
  "event": "session.status_updated",
  "delivery_id": "whd_…",
  "created_at": "2026-10-03T09:12:44Z",
  "data": {
    "session_id": "ses_…",
    "status": "approved",
    "previous_status": "processing",
    "vendor_data": "user-42",
    "workflow_id": "wf_…",
    "decision": { "status": "approved", "checks": [ … ], "identity": { … } }
  }
}

Verify every delivery before you trust it.

import { createHmac, timingSafeEqual } from "node:crypto";

// x-kyc-signature: v1=<hex(HMAC-SHA256(secret, "{timestamp}.{raw_body}"))>
export function verifyKycWebhook(rawBody, headers, secret) {
  const ts = headers["x-kyc-timestamp"] ?? "";
  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${ts}.`)
    .update(rawBody) // the raw Buffer, before JSON.parse
    .digest("hex");
  const given = String(headers["x-kyc-signature"] ?? "").replace(/^v1=/, "");

  return given.length === expected.length &&
    timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

POST to your endpoint

x-kyc-timestamp: 1767225600
x-kyc-signature: v1=5f2c…

{
  "event": "session.status_updated",
  "delivery_id": "whd_…",
  "created_at": "2026-10-03T09:12:44Z",
  "data": {
    "session_id": "ses_…",
    "status": "approved",
    "previous_status": "processing",
    "vendor_data": "user-42",
    "decision": { "status": "approved", "checks": [ … ], "identity": { … } }
  }
}

Limits

What it does not do.

Stated up front, so you can decide what to pair it with.

  • No client SDKs yet: the API is plain HTTPS and JSON, and the hosted flow is a web page, so any language and any device browser works.

FAQ

Webhooks and API: questions.

What are the standalone endpoints?

POST /v1/checks/aml, /v1/checks/pan, /v1/checks/mrz, /v1/checks/face-match and /v1/checks/aadhaar/secure-qr.

Is there a sandbox?

Every organisation starts with a sandbox app and kyc_test_ keys. Create a live app when you are ready.

Try it in the sandbox today.

Every check is available from the first sign-up, with test keys and a default workflow. Talk to us when you are ready to verify real people.