> ## 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.

# User Flow

> End-to-end Didit verification UX: ID capture, biometric liveness, smart retries, real-time feedback. Optimized to drive industry-leading completion rates.

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>;

<AgentPromptAccordion
  title="Session Lifecycle Prompt"
  prompt={`# Goal — drive a user through a full verification session

The session is the unit you build against. One \`POST /v3/session/\` returns a hosted URL; your user opens it; Didit runs the workflow; you receive a webhook with the decision.

For the complete recipe (SDK pick per platform, HMAC signature pipeline, decision parsing, every status), copy the canonical prompt at:

https://docs.didit.me/integration/integration-prompt

## Session lifecycle at a glance

| Phase | Trigger | What happens |
|---|---|---|
| Create | \`POST https://verification.didit.me/v3/session/\` | Returns \`session_id\`, \`url\`, \`session_token\`. Status = \`Not Started\`. |
| User opens \`url\` | SDK / iframe / redirect | Status flips to \`In Progress\`. The hosted flow handles country, ID capture, liveness, retries. |
| Processing | Didit backend | OCR, liveness, face match, AML, etc. run; per-feature plural arrays populate on the decision. |
| Decision | \`status.updated\` webhook | One of \`Approved\`, \`Declined\`, \`In Review\`, \`Awaiting User\` (KYB resub), \`Resubmitted\`, \`Abandoned\`, \`Expired\`, \`Kyc Expired\`. |

## Create a session

\`\`\`bash
curl -X POST https://verification.didit.me/v3/session/ \\
-H "x-api-key: $DIDIT_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{
"workflow_id": "$WORKFLOW_ID",
"vendor_data": "user-42",
"callback": "https://myapp.com/kyc/return"
}'
\`\`\`

Always pass \`vendor_data\` — it is the linking key that binds the session to the persistent [User entity](/entities/users/overview). Without it the session is orphaned.

## Receive the decision

Webhook (recommended) — register a destination with \`webhook_version: "v3"\` subscribed to \`status.updated\` and \`data.updated\`. Verify the \`X-Signature-V2\` header (HMAC-SHA256 of canonical JSON).

Polling fallback —
\`\`\`bash
curl https://verification.didit.me/v3/session/$SESSION_ID/decision/ \\
-H "x-api-key: $DIDIT_API_KEY"
\`\`\`

The V3 decision returns plural arrays for every feature: \`id_verifications[]\`, \`liveness_checks[]\`, \`face_matches[]\`, \`aml_screenings[]\`, \`nfc_verifications[]\`, \`proof_of_address_checks[]\`, etc. Never assume V2 singular fields.

## Failure modes

- Missing API key → \`401 {"detail": "Invalid or missing API key"}\`.
- Invalid \`workflow_id\` → \`400\`.
- User abandons the flow → status becomes \`Abandoned\`.
- Workflow asks for resubmit → status \`Resubmitted\`, payload includes \`resubmit_info\`; re-deliver the same \`url\`.

## Sources of truth

- /sessions-api/create-session — request body schema
- /sessions-api/retrieve-session — V3 decision schema
- /integration/verification-statuses — literal status strings
- /integration/webhooks — signature verification, retries
- /reference/data-models — per-feature decision shape
- /integration/integration-prompt — canonical end-to-end prompt
`}
/>

This guide explains what a typical end-user experiences during an **ID Document Check + Liveness Test** session, and highlights the UX optimizations that drive high completion rates.

<VideoEmbed src="https://www.youtube.com/embed/h0i9Q0-izcw?start=46&rel=0&playsinline=1" title="User Journey: Starting a Verification" />

## Flow Overview

<Frame>
  <img src="https://mintcdn.com/didit-0f962782/z6T2GHM4Zh-iSj-K/images/user-verification-flow.png?fit=max&auto=format&n=z6T2GHM4Zh-iSj-K&q=85&s=cc77846e25a476c7e91281295f079e80" alt="Didit identity verification user journey from start screen through ID and selfie capture to result" width="10936" height="6464" data-path="images/user-verification-flow.png" />
</Frame>

<Steps>
  <Step title="Start screen">
    The user lands on a clean, branded screen that sets expectations (e.g., "You'll need your ID and your face"). This reduces drop-off by removing uncertainty.
  </Step>

  <Step title="Country and document selection">
    The user selects their country and document type (Passport, ID Card, etc.).

    <Note>
      If your workflow is configured for a **single document type**, this screen is **automatically skipped** — taking the user straight to capture.
    </Note>
  </Step>

  <Step title="ID capture with intelligent retries">
    The user photographs the front of their document.
    **On-device quality check** — Before submitting, the SDK checks for glare, blur, and cutoff edges. If a problem is detected, the user is prompted to retake.

    **Backend validation with guided retry** — Once submitted:

    * **Pass** → proceed to liveness
    * **Fail** → the user sees specific instructions (e.g., "Document expired", "MRZ unreadable — retake in stronger light") rather than a generic error

    This self-serve retry loop significantly increases pass-through rates. See the full list of validation outcomes under [ID Verification Warnings](/core-technology/id-verification/warnings-id-verification).
  </Step>

  <Step title="Liveness + face match">
    A selfie liveness check ensures the user is physically present and prevents replay/deepfake attacks. If the user doesn't respond in time, the process **retries once automatically** instead of hard-failing.
  </Step>

  <Step title="Final outcome">
    After all checks complete, the user sees one of three results:

    | Result        | Description                                                         |
    | ------------- | ------------------------------------------------------------------- |
    | **Approved**  | All checks passed. Data is extracted and available via API/webhook. |
    | **Declined**  | The system determined the verification cannot be approved.          |
    | **In Review** | Automated checks were inconclusive — requires manual review.        |
  </Step>
</Steps>

By combining smart UX, progressive validation, actionable feedback, and graceful retries, the Didit verification journey keeps good users moving and keeps bad actors out — without manual intervention.
