> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Biometric Authentication

> Authenticate returning users with liveness + face match. No documents, sub-2-second passwordless verification. Pay-per-call $0.10.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

export const VideoEmbed = ({src, title = "Video", type = "iframe"}) => <div className={type === "iframe" ? "didit-video-embed" : "didit-video-embed didit-video-native"}>
    {type === "iframe" ? <iframe src={src} title={title} style={{
  width: "100%",
  height: "100%",
  border: 0,
  borderRadius: "12px"
}} allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen /> : <video controls autoPlay muted loop playsInline src={src} title={title} style={{
  width: "100%",
  height: "auto",
  display: "block",
  borderRadius: "12px"
}} />}
  </div>;

Didit's Biometric Authentication solution provides a streamlined verification experience for returning users. This workflow can be configured to perform a liveness-only check for simple presence verification, or combine liveness with facial recognition for stronger identity confirmation against a stored portrait. This flexibility creates a frictionless experience while maintaining high security standards.

<AgentPromptAccordion
  title="Biometric Authentication Integration Prompt"
  prompt={`# Goal
Integrate Didit Biometric Authentication (passwordless re-verification of returning users) into your app.

# Delivery mode
Biometric Authentication is **session-only**. There is no dedicated /v3/biometric-auth/ endpoint. It is delivered through a session whose workflow is configured for biometric-auth use (workflow_type=BIOMETRIC_AUTHENTICATION). Under the hood it reuses the same LivenessV3 + FaceMatchV3 serializers as the regular KYC flow — the only difference is the reference image source: the previously stored portrait of the user (looked up by vendor_data).

# Steps
1. Create a Biometric Auth workflow in the Business Console (workflow_type=BIOMETRIC_AUTHENTICATION). Configure the LIVENESS method (PASSIVE / ACTIVE_3D / FLASHING) and the FACE_MATCH similarity threshold.
2. The returning user must already have a prior KYC session under the same \`vendor_data\` — that session's portrait is used as the reference image.
3. Create a session — POST /v3/session/ with { workflow_id, vendor_data, callback }. Use the **same vendor_data** as the user's prior KYC session so the stored portrait can be retrieved.
4. Open session.url for the user (or mount the Web/Mobile SDK).
5. Fetch the decision — GET /v3/session/{sessionId}/decision/ or subscribe to session.status.updated.

curl example:
\`\`\`bash
curl -X POST 'https://verification.didit.me/v3/session/' \\
-H 'x-api-key: YOUR_API_KEY' \\
-H 'Content-Type: application/json' \\
-d '{
"workflow_id": "YOUR_BIOMETRIC_AUTH_WORKFLOW_ID",
"vendor_data": "user-1234",
"callback": "https://yourapp.com/post-reauth"
}'
\`\`\`

# Decision surface
Biometric Authentication surfaces results across **two arrays** on the decision payload — there is no biometric_auths[] array:
- \`liveness_checks[]\` — the live capture (status + score + warnings under LogWarningChoices.LIVENESS).
- \`face_matches[]\` — the comparison against the stored portrait (status + score + warnings under LogWarningChoices.FACEMATCH).

Approve the user only when **both** entries are status="Approved".

# Status enum (per liveness_checks[].status and face_matches[].status)
"Not Started" | "In Progress" | "Approved" | "In Review" | "Declined" | "Abandoned" | "Kyc Expired"

# Warnings
- Liveness side (LogWarningChoices.LIVENESS): LOW_LIVENESS_SCORE, LIVENESS_FACE_ATTACK, NO_FACE_DETECTED, MULTIPLE_FACES_DETECTED, FACE_IN_BLOCKLIST, DUPLICATED_FACE.
- Face Match side (LogWarningChoices.FACEMATCH): LOW_FACE_MATCH_SIMILARITY, NO_REFERENCE_IMAGE.
Full catalogue: /core-technology/biometric-auth/warnings-biometric-authentication.

# Failure modes to handle
- NO_REFERENCE_IMAGE on face_matches[] — the user has no prior KYC session under this vendor_data; route them to first-time KYC instead.
- LOW_FACE_MATCH_SIMILARITY — the live selfie does not match the stored portrait; do not authenticate.
- LIVENESS_FACE_ATTACK — presentation attack detected; reject and flag the account.
- FACE_IN_BLOCKLIST — user has been added to your blocklist since enrolment; block.

# See also
- Canonical schema: /reference/data-models#biometric-authentication (and /reference/data-models#liveness-check, /reference/data-models#face-match)
- Per-feature report: /core-technology/biometric-auth/report-biometric-authentication
- Risk catalogue: /core-technology/biometric-auth/warnings-biometric-authentication
- Full integration playbook: /integration/integration-prompt`}
/>

<VideoEmbed src="https://www.youtube.com/embed/h0i9Q0-izcw?start=604&rel=0&playsinline=1" title="Face Matching & Biometric Authentication" />

## Key Features

#### Fast Re-Verification

* No document scanning required
* Complete verification in seconds
* Reduces user friction and abandonment

#### Advanced Security

* Uses the same neural network architecture as Face Match 1:1
* Prevents account takeover attempts
* Includes liveness detection to prevent spoofing

#### Integration Flexibility

* Available as web-based
* Configurable matching thresholds
* Optional Device & IP Analysis for enhanced security

## How It Works

<Steps>
  <Step title="Session Creation" icon="circle-play">
    When you create a biometric authentication session:

    * For workflows with **face matching**, provide the `portrait_image` in Base64 (from a previous verification or your own database)
    * If you **omit** `portrait_image`, the system performs a **liveness-only check**
    * The biometric authentication workflow is initialized

    ```json theme={null}
    {
      "workflow_id": "11111111-2222-3333-4444-555555555555",
      "vendor_data": "user-123",
      "callback": "https://example.com/verification/callback",
      "metadata": { "login_attempt": "2" },
      "portrait_image": "/9j/4AAQSkZJRgABAQEAyQDJAAD/2...Y+QrTcpH/9k="
    }
    ```
  </Step>

  <Step title="Live Photo Capture" icon="camera">
    During the authentication process:

    | Check                     | Description                                                    |
    | ------------------------- | -------------------------------------------------------------- |
    | **Liveness verification** | Prevents spoofing using Passive Liveness or 3D Action & Flash  |
    | **Image quality**         | System evaluates lighting, positioning, and clarity            |
    | **Retry guidance**        | Poor quality images are rejected with improvement instructions |
    | **Real-time feedback**    | User sees positioning guides for optimal capture               |
  </Step>

  <Step title="Verification & Result Processing" icon="shield-check">
    The system processes the verification based on your workflow configuration:

    <AccordionGroup>
      <Accordion title="Liveness-Only Mode" icon="face-viewfinder">
        If no `portrait_image` is provided, the system confirms the user's liveness. A successful check results in an **approved** authentication — useful for simple presence verification.
      </Accordion>

      <Accordion title="Liveness + Face Match Mode" icon="user-check">
        If a `portrait_image` is provided:

        1. System performs the **liveness check** first
        2. If liveness passes, compares the new selfie with the stored portrait
        3. A **similarity score (0–100%)** is generated
        4. Score above your configured threshold → **Approved**
        5. Score below threshold → **Declined**
      </Accordion>
    </AccordionGroup>

    Results are available via **API response**, **Business Console**, and **webhooks**.
  </Step>
</Steps>
