Skip to content

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.

  1. 1Your backend → KYCVerify APIPOST /v1/sessionsx-api-key; returns url, session_id
  2. 2Your backend → Hosted flowSend the urlredirect, email or QR code
  3. 3Hosted flow → KYCVerify APIConsent, document, liveness/flow/{token}/… per enabled step
  4. 4Hosted flow → KYCVerify APIPOST /flow/{token}/submitstatus becomes processing
  5. 5KYCVerify APIBackground checksface match, AML, age, duplicate, IP
  6. 6KYCVerify API → Your webhooksession.status_updatedHMAC-SHA256 signed, decision included
  7. 7KYCVerify API → Hosted flowReturn to callback_url?session_id=…&status=…
  8. 8Your backend → KYCVerify APIGET /v1/sessions/{id}/decisionany time, optional
The whole integration: two calls from your backend, one link for the person, one signed event back.

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

  1. Open the url from step 2 on your phone, or on a laptop and use the QR code it shows to move to your phone.
  2. Photograph a passport or ID card, complete the head-turn challenges and submit.
  3. Watch the session move to processing and then a final status in the console's Sessions page.
  4. Check your endpoint received session.status_updated and that your verifier returned true.

Next