Get started
Quickstart
Create a session, send the link, verify the signed webhook and read the decision.
A verification in KYCVerify is a session. Your backend creates it with an API key, the person completes the hosted flow in their browser, the engine runs the checks and reaches a decision, and a signed webhook tells your backend. This page takes you round that loop once, with a sandbox key.
Your backend
KYCVerify API
Hosted flow
Your webhook
- 1POST /v1/sessionsYour backend to KYCVerify API: x-api-key; returns url, session_id
- 2Send the urlYour backend to Hosted flow: redirect, email or QR code
- 3Consent, document, livenessHosted flow to KYCVerify API: /flow/{token}/… per enabled step
- 4POST /flow/{token}/submitHosted flow to KYCVerify API: status becomes processing
- 5Background checksKYCVerify API to KYCVerify API: face match, AML, age, duplicate, IP
- 6session.status_updatedKYCVerify API to Your webhook: HMAC-SHA256 signed, decision included
- 7Return to callback_urlKYCVerify API to Hosted flow: ?session_id=…&status=…
- 8GET /v1/sessions/{id}/decisionYour backend to KYCVerify API: any time, optional
- 1Your backend → KYCVerify APIPOST /v1/sessionsx-api-key; returns url, session_id
- 2Your backend → Hosted flowSend the urlredirect, email or QR code
- 3Hosted flow → KYCVerify APIConsent, document, liveness/flow/{token}/… per enabled step
- 4Hosted flow → KYCVerify APIPOST /flow/{token}/submitstatus becomes processing
- 5KYCVerify APIBackground checksface match, AML, age, duplicate, IP
- 6KYCVerify API → Your webhooksession.status_updatedHMAC-SHA256 signed, decision included
- 7KYCVerify API → Hosted flowReturn to callback_url?session_id=…&status=…
- 8Your backend → KYCVerify APIGET /v1/sessions/{id}/decisionany time, optional
1. Get a test key
Sign up to create an organisation. You get an owner account, a sandbox app and a default workflow. In the console open Developers → API keys and create a key. It starts with kyc_test_ and is shown once; store it as KYC_API_KEY in your backend's secrets.
2. Create a session
POST /v1/sessions
curl -X POST https://kycverify.me/api/v1/sessions \
-H "x-api-key: $KYC_API_KEY" \
-H "content-type: application/json" \
-d '{
"vendor_data": "user-42",
"contact": { "email": "anna@example.com" }
}'201 Created
{
"session_id": "ses_0k3v9x2m4a7qhd8f1rtb",
"status": "not_started",
"url": "https://kycverify.me/verify/q3Xf…",
"session_token": "q3Xf…",
"workflow_id": "wf_0k3t1c8n5e2wpzr6g4ya",
"vendor_data": "user-42",
"expires_at": "2026-10-10T09:12:44Z"
}Send url to the person: redirect them, email it, show a QR code. Keep session_id with your user record. vendor_data is echoed in every webhook so you can match an event to a user without a lookup.
3. Receive the webhook
In the console's Webhooks page, set your endpoint URL, then press Rotate secret to reveal a signing secret (whsec_…). It is shown once; store it as KYC_WEBHOOK_SECRET. Send test delivers a ping. From then on, every status change of every session is delivered as session.status_updated, signed over the timestamp and the raw body.
Verify x-kyc-signature
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));
}4. Read the decision
When the status is final (approved, declined or in_review) the webhook's data.decision holds the full decision. You can also fetch it at any time:
GET /v1/sessions/{id}/decision
curl https://kycverify.me/api/v1/sessions/ses_0k3v9x2m4a7qhd8f1rtb/decision \
-H "x-api-key: $KYC_API_KEY"200 OK
{
"session_id": "ses_0k3v9x2m4a7qhd8f1rtb",
"status": "approved",
"decision_reason": null,
"workflow_id": "wf_0k3t1c8n5e2wpzr6g4ya",
"vendor_data": "user-42",
"metadata": {},
"identity": {
"full_name": "ANNA MARIA ERIKSSON",
"date_of_birth": "1974-08-12",
"nationality": "UTO",
"document_type": "passport",
"document_number_masked": "L8989****",
"expiry_date": "2032-04-15",
"sources": { "full_name": "mrz", "date_of_birth": "mrz" }
},
"checks": [
{ "kind": "document", "status": "passed", "score": 0.97 },
{ "kind": "liveness", "status": "passed", "score": 0.91 },
{ "kind": "face_match", "status": "passed", "score": 0.612 },
{ "kind": "aml", "status": "passed", "score": 0 }
],
"review": null,
"created_at": "2026-10-03T09:02:11Z",
"submitted_at": "2026-10-03T09:11:58Z",
"completed_at": "2026-10-03T09:12:44Z",
"expires_at": "2026-10-10T09:02:11Z"
}Try it end to end
- Open the
urlfrom step 2 on your phone, or on a laptop and use the QR code it shows to move to your phone. - Photograph a passport or ID card, complete the head-turn challenges and submit.
- Watch the session move to
processingand then a final status in the console's Sessions page. - Check your endpoint received
session.status_updatedand that your verifier returned true.
Next
- Concepts: the objects and words used everywhere else
- API reference: every endpoint with examples
- Workflows: choose the steps and thresholds
- Data handling: what is stored, encrypted and purged