Live ID ValidationLive ID API reference

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 }
FieldTypeRequiredNotes
enrollmentIdstringone ofThe Live ID enrollment id.
applicantIdstringone ofThe KYC applicant id (resolved to its enrollment).
labelstringnoFree-text label for your own reference.
expiresInDaysintegerno1–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 /open keep 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

StatusCodeMeaning
401invalid_key / unknown_key / key_mismatchThe Identification Key is tampered, unrecognised, or its claims don't match.
403key_revoked / key_expiredThe key was revoked or has expired.
403live_id_disabledLive ID is not enabled for the tenant.
402verification_unavailableOver 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.
409enrollment_inactiveThe profile is not (or no longer) enrolled.
429enrollment_lockedToo many failed validations — try again after the lock expires.
404unknown_sessionNo session for that token.
409session_consumedThe session was already used / is in progress.
410session_expiredThe session token expired before the selfie arrived.
503biometric_unconfiguredThe tenant's biometric provider isn't configured.