Live ID API reference
Endpoints for Live ID Validation — issue an Identification Key, mint a capture session, open the hosted capture page, submit a selfie, poll the result, and the validation webhook payload.
Three surfaces: key management (dashboard-authenticated, admin), the session lifecycle (key- and session-token-authenticated, public), and the webhook. The base URL below is your tenant host.
The session endpoints live under /api/kyc/public/live-id and are not
API-key authenticated — they are gated by the Identification Key (mint) or
the single-use session token (validate / poll), and rate-limited per IP.
Issue an Identification Key
POST /api/kyc/live-id/:orgSlug/keys Auth: dashboard session (admin/owner)
Issues a per-profile key. The plaintext token is returned once — store it securely on your backend; only its hash is kept.
Request body
{ "applicantId": "app_…", "label": "mobile-app", "expiresInDays": 365 }
| Field | Type | Required | Notes |
|---|---|---|---|
enrollmentId | string | one of | The Live ID enrollment id. |
applicantId | string | one of | The KYC applicant id (resolved to its enrollment). |
label | string | no | Free-text label for your own reference. |
expiresInDays | integer | no | 1–3650. Omit for no expiry. |
Response 201
{
"id": "lik_…",
"token": "livid_…", // shown ONCE
"keyVersion": 1,
"label": "mobile-app",
"expiresAt": null,
"createdAt": "2026-06-18T12:00:00.000Z"
}
Companion endpoints: GET /api/kyc/live-id/:orgSlug/keys (list, metadata only),
DELETE /api/kyc/live-id/:orgSlug/keys/:id (revoke),
GET /api/kyc/live-id/:orgSlug/enrollments (list enrolled profiles).
Mint a session
POST /api/kyc/public/live-id/sessions Auth: Identification Key (in body)
Call this server-side. Verifies the key, applies the billing gate, and mints a single-use session token.
Request body
{ "key": "livid_…", "deviceFingerprint": "optional", "captureMethod": "live_camera" }
Response 201
{
"sessionId": "lis_…",
"sessionToken": "…",
"expiresAt": "2026-06-18T12:02:00.000Z",
"captureUrl": "https://app.certivant.com/live-id/…",
"nonce": "…"
}
Hand captureUrl to the device. The sessionToken is also returned so you can
poll status server-side. nonce is the session's one-time value — only needed
if you build your own capture UI (see Submit a selfie).
Minting is refused with 429 enrollment_locked while the profile is locked
after repeated failed validations (5 failures within 15 minutes lock it for 30
minutes and fire live_id.enrollment.locked).
Open the capture page
GET /live-id/:sessionToken Auth: session token (in path)
The hosted, mobile-first capture page — open it top-level: in a popup, via a
redirect, or in a native WebView. It is served with frame-ancestors 'none', so
it is not iframe-embeddable. It shows the camera + selfie UI when the session
is live, and a clear notice when expired / invalid / already completed.
When it loads, the hosted page calls
POST /api/kyc/public/live-id/sessions/:sessionToken/open. That records the
device's IP address and returns the session's one-time value, which the page
sends back with the selfie. From that point on, a selfie submitted without the
right value is refused.
Open a capture (own capture UI only)
POST /api/kyc/public/live-id/sessions/:sessionToken/open Auth: session token (in path)
Call this from the device when your own capture screen opens. Response 200:
{ "nonce": "…", "expiresAt": "2026-06-18T12:02:00.000Z" }
Submit a selfie
POST /api/kyc/public/live-id/validate Auth: session token (in body)
The hosted page calls this for you. Document it only if you build your own native capture UI instead of the hosted page.
Request body
{ "sessionToken": "…", "selfieImage": "data:image/jpeg;base64,/9j/4AAQ…", "nonce": "…" }
selfieImage accepts a base64 string or a data: URL. Min ~1KB, max 8MB.
nonce is the session's one-time value (from the mint response or from
/open):
- A wrong value is always refused (
reason: "nonce_mismatch",matched: false). - A missing value is refused only when the capture was opened with
/open(the hosted page always does). Server-to-server integrations that never call/openkeep working without it — but sending it is recommended.
The same selfie can never be used twice: an exact copy of the enrolled reference
photo or of an earlier session's selfie is refused (reason: "replayed_image").
Near-identical copies are reported in the webhook's riskSignals.
Response 200
{
"matched": true,
"faceMatchScore": 0.94,
"livenessScore": 0.98,
"livePassed": true,
"reason": "matched",
"sessionId": "lis_…"
}
reason is one of matched, face_below_threshold, liveness_failed,
face_and_liveness_failed, no_reference_image, nonce_mismatch,
replayed_image, near_duplicate_image.
Poll a session
GET /api/kyc/public/live-id/sessions/:sessionToken Auth: session token (in path)
Response 200
{
"sessionId": "lis_…",
"status": "completed",
"matched": true,
"faceMatchScore": 0.94,
"livenessScore": 0.98,
"reason": "matched",
"expiresAt": "2026-06-18T12:02:00.000Z"
}
status: pending → processing → completed (or expired). When
completed, matched is the verdict. Poll every ~2s; stop on completed or
expired.
Webhook — live_id.validation.completed
Fires once per completed validation to your tenant webhook URL, signed exactly
like every other Certivant webhook (X-Certivant-Signature-V2: t=…,v1=…; the older
X-KYC-Signature: sha256=… is still sent).
{
"event": "live_id.validation.completed",
"organizationId": "org_…",
"validationId": "liv_…",
"sessionId": "lis_…",
"enrollmentId": "lie_…",
"applicantId": "app_…",
"matched": true,
"faceMatchScore": 0.94,
"livenessScore": 0.98,
"decisionReason": "matched",
"riskSignals": {
"nearDuplicateImage": false,
"nearestImageDistance": 0.21,
"nearestImageSource": "previous_session",
"nonceVerified": true,
"deviceIpChanged": false
},
"timestamp": "2026-06-18T12:02:00.000Z"
}
riskSignals are for your own risk decision — they never change matched on
their own. nonceVerified is true when the selfie carried the session's
one-time value; deviceIpChanged is true when the device's IP changed between
/open and the selfie (null when it can't be compared).
Webhook — live_id.enrollment.locked
Fires when a profile is locked after repeated failed validations.
{
"event": "live_id.enrollment.locked",
"organizationId": "org_…",
"enrollmentId": "lie_…",
"applicantId": "app_…",
"failures": 5,
"lockedUntil": "2026-06-18T12:32:00.000Z",
"timestamp": "2026-06-18T12:02:00.000Z"
}
See Webhooks → Signature verification.
Errors
| Status | Code | Meaning |
|---|---|---|
401 | invalid_key / unknown_key / key_mismatch | The Identification Key is tampered, unrecognised, or its claims don't match. |
403 | key_revoked / key_expired | The key was revoked or has expired. |
403 | live_id_disabled | Live ID is not enabled for the tenant. |
402 | verification_unavailable | Over quota and the wallet can't cover the PAYG charge (continue-without-credit off). The body also carries error.organization (name, logoUrl); the message never mentions billing, so it can be shown to your user. |
409 | enrollment_inactive | The profile is not (or no longer) enrolled. |
429 | enrollment_locked | Too many failed validations — try again after the lock expires. |
404 | unknown_session | No session for that token. |
409 | session_consumed | The session was already used / is in progress. |
410 | session_expired | The session token expired before the selfie arrived. |
503 | biometric_unconfigured | The tenant's biometric provider isn't configured. |
Live ID Validation
Add biometric logon to your app — confirm a returning user by matching a live selfie against their already-verified Certivant KYC face, and gate access on a simple matched true/false.
Mobile onboarding
Embed the Live ID hosted capture page in a native app — WebView setup and camera permissions for iOS, Android, and .NET MAUI, plus ready-to-run sample apps.