Liveness & Face Match
Check that a live person is in front of the camera and, optionally, that they match a reference photo
Status: prototype. The liveness module is an anti-fraud layer. It raises the cost of replay, injection and synthetic-media attacks; it is not a certified presentation attack detection product, and a
passverdict is evidence to combine with your other onboarding measures, not proof on its own. What it is designed to raise the cost of, and what it does not cover, is in the threat model.
The module records a short capture (a video plus a few still frames) from the subject's camera and analyses it server-side. Nothing the client reports is trusted: every liveness fact is re-derived from the uploaded media. An active capture takes about a minute of the subject's time, a passive one a few seconds.
| Check family | What it verifies |
|---|---|
| Challenge | That the subject followed a script of timed prompts issued by the server for this session, re-verified from the uploaded frames and from the video. Every active profile runs the same script family, on a laptop webcam as on a phone |
| Temporal | One face per frame, one identity across the whole capture, frames that belong to the video, media not seen before |
| Forensics | A third-party forensic analysis of the video, of selected frames and of the reference photo |
| Face match | Optional 1:1 comparison between a reference photo and the most frontal frame of the capture |
The result is a verdict (pass, review or fail), a risk score from 0 to 100 and a per-check report.
Two ways to integrate
| Mode | You provide | The platform provides | Key |
|---|---|---|---|
| Hosted flow | A session, and the capture link handed to the subject | The capture page (consent, camera, timed prompts, upload), the challenge protocol, the analysis, the report, the return to your page and the completion webhook | Publishable or secret |
| API-only | The media (a video and/or frames, optionally a reference photo) posted from your backend | The analysis and the report; no challenge, since the platform did not drive the capture | Secret |
The hosted flow is the one with challenge verification. API-only analysis runs the passive checks alone (forensics, temporal consistency, face match) and is meant for media captured by your own application.
Hosted flow
- Create a session with
POST /liveness/sessions, choosing a profile. The response carriessessionId, a one-timesessionToken, thecaptureUrlandexpiresIn: 600(10 minutes to start the capture). - Hand the capture link to the subject. Show
captureUrlas a QR code for a phone handoff, or open it on the same device. The session token travels only in the URL fragment (#s=<sessionId>.<sessionToken>), so it never reaches server logs or aRefererheader. Do not copy it into a query string. - Optionally upload a reference photo (
POST /liveness/sessions/:id/referencewith your API key, or from the capture page with the session token) before the capture. Profiles whosereference.requiredistruereject a capture without one. - The subject captures. The hosted page collects consent, records the camera through the prompts and uploads the media in one request. Allow about a minute for an active profile. A session accepts one capture at a time, and up to three in all: when the platform cannot measure the subject's images reliably, it asks the subject to do it again on a fresh challenge instead of deciding on a weaker measurement (see retakes).
- Get told the session is over. Configure a completion webhook once and the platform posts the session id, status, verdict and risk score as soon as the session is terminal, signed and retried. Polling
GET /liveness/sessions/:idevery couple of seconds is still supported and still the authoritative read; whileprocessing, the document carriesprogress: { stage, percent }. - Bring the subject back. Pass a
returnUrlwhen you create the session and the capture page offers the subject a way back to your page when they are done. It carries the session id and nothing else: the verdict is read by your back end, never handed to the browser. - Act on
verdict,riskScoreand, for review cases, thecheckslist.
Steps 5 and 6 are the two halves of the handover, and they are not interchangeable: the webhook is how your system learns the outcome, the return URL is how the subject gets back to your flow. The browser never carries the result.
The capture page is ours, and the challenge protocol it runs is not part of the public contract. Media captured by your own application goes through API-only analysis instead, which carries no challenge checks.
Session lifecycle
| Status | Meaning |
|---|---|
created | Session exists, capture not started |
challenged | The capture page has taken the session in hand |
processing | The capture was accepted and the pipeline is running |
retake | The capture could not be measured reliably and nothing failed: the subject is asked to capture again, on a fresh challenge. Not a verdict |
completed | The report is available |
error | The pipeline failed (failureReason says why) |
expired | No capture arrived within 10 minutes of creation, or of the last retake |
completed, error and expired are terminal, and each fires the completion webhook once if one is configured. retake is not: it moves back to challenged when the subject starts again, fires no webhook and carries no verdict. A session allows 3 capture attempts; a third attempt that cannot be measured either completes the session with verdict: "fail" and failureReason: "images_unmeasurable". So a retake is never mistaken for a failure: a failure is always completed with verdict: "fail". expired is written by a sweep that runs every 5 minutes (the same sweep times out a pipeline that overran its budget), so its webhook lands up to 5 minutes after the deadline; GET /liveness/sessions/:id reports expired as soon as the deadline has passed.
API-only sessions created through POST /liveness/analyze go straight to processing and come back completed in the same response.
Attested capture
The hosted page is a browser: the platform verifies what it uploads, and the threat model says what a browser cannot promise about where the bytes came from. The mobile-attested profile runs the capture in the Verifiables mobile application instead. The app records, digests the exact bytes it is about to upload, and signs those digests together with a single-use nonce the platform issued for this session, using the device's hardware-backed app attestation. The platform checks the digests against what it received, verifies the signature, and reports the outcome as the attestation.device check, which is required on that profile.
What it proves: that this application, unmodified, on an intact device, produced these bytes after this nonce. What it does not prove: what the camera saw. It is a provenance fact about the upload, to be read together with the other checks, not a certified presentation attack detection, a proof of presence or an identity-proofing path on its own. iOS is verified today; an Android capture on this profile fails the required check with platform_unsupported, never a pass and never a silent skip. The sessions are created, handed over and read exactly like any other, with a capture link the app recognises: see the subject endpoints for the two additions to the capture contract.
Degraded modes
The pipeline never aborts because a provider is missing: the affected checks get the status unavailable, and the profile's unavailablePolicy decides what that does to the verdict (review, fail or ignore). This includes the head-angle checks of an active profile when the face landmark model is not available on our side (reason mesh_engine_unavailable): an outage of ours is reported as such, never turned into a retake the subject cannot fix. GET /liveness/status reports which providers are ready, so you can surface a degraded state in your own health checks.
Data retention
- The consent version and acceptance timestamp sent by the capture page are stored with the session and echoed in the report (
client.consent). - Media artifacts (video, frames, reference photo, forensic heatmaps) are kept for
retention.artifactsHoursafter the capture, 24 hours by default and configurable per profile. A purge job then drops their bytes; the report keeps their ids, hashes and sizes withpurged: true, and the artifact endpoint answers410. - The report JSON contains no binaries and is not purged by the artifact job.
- The
temporal.media_reusecheck compares the upload against your organisation's retained artifacts, so its reach is bounded by the retention window: a shortretention.artifactsHoursshortens the memory of that check. - Face embeddings and decoded pixels never leave process memory. Session tokens are stored as a hash only. Media sent to the forensic stage is deleted on its side once the analysis returns.
- Raw capture media (
video,video_mp4,referenceartifacts) is served to secret keys only; frames and heatmaps are served to publishable keys as well.