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.
- delivery attempts over about 10 h 43 min
- 8
- timestamp tolerance for receivers
- 5 min
- standalone check endpoints
- 5
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}")>withx-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_andkyc_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
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
Timestamp tolerance
Waiting for the clock
Logic ported from backend/crates/kyc-api/src/webhooks.rs. Nothing you type leaves this page.
How it works
What happens, in order.
- 1
Create
POST /v1/sessionsreturns the session id, the hostedurland its expiry. - 2
Verify
The person completes the hosted flow at
/verify/{token}. - 3
Receive
Your webhook gets the status change, with the full decision once the session is final.
GET /v1/sessions/{id}/decisionfetches 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.
| Method | Path | What it does |
|---|---|---|
| POST | /v1/sessions | Create a session; returns the hosted url |
| GET | /v1/sessions | List sessions, paginated |
| GET | /v1/sessions/{id} | One session's summary |
| GET | /v1/sessions/{id}/decision | Status, identity, every check, the review |
| PATCH | /v1/sessions/{id}/status | Approve or decline a session in review |
| DELETE | /v1/sessions/{id} | Purge a session's data now |
| GET | /v1/workflows | The app's workflows |
| POST | /v1/checks/aml | Screen a name |
| POST | /v1/checks/pan | Validate a PAN |
| POST | /v1/checks/mrz | Parse an MRZ |
| POST | /v1/checks/face-match | Compare two face images |
| POST | /v1/checks/aadhaar/secure-qr | Verify an Aadhaar Secure QR |
Delivery and signing
What a receiver sees, and when KYCVerify tries again.
| Code | Outcome | When |
|---|---|---|
| +30 s, 2 m, 10 m, 30 m | retries 1-4 | After any non-2xx response or a 10-second timeout. |
| +1 h, 3 h, 6 h | retries 5-7 | Persisted, so they survive a restart. Eight attempts in all over about 10 h 43 min, then the delivery is marked failed. |
| x-kyc-timestamp | header | Unix seconds when the delivery was signed; reject anything older than 5 minutes. |
| x-kyc-signature | header | v1= 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
{
"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.

