Skip to main content

Overview

Warnings on the liveness report flag every condition Didit observed while running the liveness check. They land in the warnings[] array on each item of liveness_checks[] (see Liveness report), with feature set to "LIVENESS". Each entry follows the shared warning object shape: feature, risk, additional_data, log_type, short_description, long_description, node_id. Every warning has three layers:
  1. The risk code — a stable identifier you can match on in your code (the codes listed below).
  2. The log_type — one of information, warning, or error. Auto-decline risks always carry error. For configurable risks it is derived from the configured action: No action → information, Review → warning, Decline → error. For threshold-driven risks (LOW_LIVENESS_SCORE, LOW_FACE_QUALITY) it is error when the value crosses the decline threshold and warning when it only crosses the review threshold.
  3. The decision impact — the same routing drives the liveness report’s status: a Review action moves it to In Review, a Decline action (or any auto-decline risk) to Declined, and No action leaves it untouched.
Some risks are emitted only when the liveness method is PASSIVE (multiple-faces, quality, and luminance checks). The tables below list the full set of liveness risk codes and their severities — no other liveness risk codes exist.

Auto-decline conditions

The following risks always force the liveness report (and the session) to Declined, with log_type: "error": When NO_FACE_DETECTED fires, LOW_LIVENESS_SCORE is suppressed (there is no face to score).

Configurable verification settings

In the Didit console, liveness risks are grouped under the following workflow settings:

Warnings produced

Core liveness

Capture channel integrity

Injection-attack signals reported by the client at capture time. Unlike presentation attacks (an artefact held in front of a real camera), these describe the camera feed itself being replaced or intercepted. Each is configurable per workflow and defaults to no action, so enabling the feature changes no decisions until you opt in.
Browsers expose no OS-level camera-source attestation to any vendor, so on web these signals are corroborating evidence rather than proof. On web, a verdict of null means not determined (device labels are unavailable until camera permission is granted) and never fires a risk.
A virtual camera is identified by its device name, so a camera the operating system presents under an ordinary name is not recognised that way. Inside an Android emulator the remapped camera is labelled like any built-in one, which is why the emulated-hardware case is reported through FRAME_INJECTION_SUSPECTED instead: it is detected from the device environment - the rendering stack and the device model reported in the user agent - rather than the camera name. Findings raised from the device environment carry additional_data.reason: "emulated_capture_device", distinguishing them from the liveness model’s own frame analysis.
Detecting the machine from a browser is evidence, not attestation, and a determined attacker can interfere with it. The native mobile SDKs report stronger, operating-system-level integrity signals a browser cannot see (emulator, rooted or jailbroken device, runtime hooking, attached debugger), each with its own configurable action under Device and runtime integrity. These signals raise the attacker’s cost considerably, but on their own they do not prove that a capture came from a genuine device. Device integrity signals are independent of the liveness method, so they combine with any of Passive, 3D Flash or 3D Action & Flash.

Recording sanity forensics

LIVENESS_VIDEO_ANOMALY is computed server-side on the stored liveness recording, independently of the liveness model. It detects a moving sequence repeated across multiple cycles, consistent with a looped feed. Set the action on your Liveness or Age Estimation node: NO_ACTION records an information finding, REVIEW can escalate a decided verification to In Review, and DECLINE can escalate it to Declined. The check runs asynchronously, so an escalation can arrive after the initial result. A more severe existing decision is not downgraded, and manual review decisions are preserved. Version 2 findings include forensics_version: 2, repeated_sequence evidence and repeated_moving_sequence in anomaly_reasons. Delivery measurements such as unique_frame_ratio, static_pair_ratio, decoded_fps_ratio and max_frame_gap_seconds remain diagnostics; they cannot establish a replay on their own. Older findings describe the detector version used at the time and are not retroactively reclassified.
This is a conservative additional signal, not complete detection of synthetic media or injected video. A recording with no anomaly is not proof of liveness. Short or ambiguous captures and captures without usable replay evidence remain unflagged by this check. Missing or unreadable recordings produce no finding from this check; other verification checks still apply.

Cross-session face matching

For matches against imported faces or list-entry faces (which have no session), the *_session_id, *_session_number, and api_service values in additional_data are null. Faces belonging to the same user (same vendor_data, or the same vendor user when available) are excluded from the entire index search — both duplicate detection and blocklist screening. Match precedence is: blocklist exact → allowlist exact → duplicate exact → blocklist possible → allowlist possible → duplicate possible — only one of these six mutually exclusive codes appears per report. DUPLICATED_FACE_NAME_MISMATCH is not part of that precedence group: it is an additional code emitted on top of DUPLICATED_FACE when the identity check also fails, never in its place. additional_data.name_match_score never carries the names being compared — only ids and a score, so no PII lands in the warning payload.

Passive liveness quality (only when method = "PASSIVE")

LOW_FACE_LUMINANCE and HIGH_FACE_LUMINANCE are mutually exclusive — the luminance is either below the minimum or above the maximum, never both.

Age estimation (adaptive workflows and AGE_ESTIMATION nodes)

Exact description strings

The API returns these exact short_description and long_description strings for each risk:

Standalone passive liveness API

The standalone Liveness API reuses the same risk codes with a few behavioral differences:
  • NO_FACE_DETECTED and LOW_LIVENESS_SCORE are mutually exclusive — the score check only runs when a face was found. LOW_LIVENESS_SCORE fires against the request’s face_liveness_score_decline_threshold (default 30) and always carries log_type: "error".
  • POSSIBLE_FACE_IN_BLOCKLIST is treated as a decline (log_type: "error") instead of review.
  • MULTIPLE_FACES_DETECTED carries log_type: "warning", while the duplicate-face risks stay informational. Only error-level warnings flip the standalone response’s status to Declined.
  • Standalone warning entries include feature but no node_id.

Examples

Auto-decline on detected attack

Configurable warnings on a passive capture

Duplicate face with payload

Here the duplicate-face action is set to Review, so the warning carries log_type: "warning" and the liveness report’s status is In Review. With the default (No action) it would carry log_type: "information" instead. api_service is null because the matched face came from a workflow session, not a standalone API call.

Duplicate face under a different name

DUPLICATED_FACE stays at its own configured action (here, No action — log_type: "information") while DUPLICATED_FACE_NAME_MISMATCH carries its own action, defaulting to Review (log_type: "warning"). The liveness report’s status follows the most severe of the two: In Review here, even though the plain duplicate-face setting alone would not have flagged it.

Recording sanity anomaly with its measurements

This example shows the replay evidence from a recording containing three copies of a moving sequence. log_type is information because the configured action is NO_ACTION. The frame-delivery diagnostics, omitted from this example for brevity, do not trigger the finding.

Warning types

Each risk is assigned a severity based on your application’s configuration. The three severities are: