---
title: "ID Capture Module"
url: "https://developer.incode.com/sdk-reference/web-sdk-2-module-id-capture/"
section: "sdk-reference"
group: "Incode Web SDK 2 Reference / Web SDK 2 Individual Modules"
version: "v1.1"
status: "live"
---
# ID Capture Module

:::note
This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation [here](/sdk-reference/web-sdk-reference).  Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade. <br /><br />Full rollout to all clients still TBD.
:::

The ID Capture module captures front and back images of identity documents (ID cards, passports) with ML-powered quality checks and validation.

> Follows the [camera-capture pattern](/sdk-reference/web-sdk-2-module-patterns/#2-camera-capture-modules), plus dedicated states for mandatory consent and manual file upload. See the patterns page for the shared lifecycle; the rest of this page covers ID-specific config, capture properties, and methods.

## Tag

`<incode-id>` is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.

```ts
import '@incodetech/web/id';
import '@incodetech/web/id/styles.css';
```

## Properties

Set these as JavaScript properties on the element (not as HTML attributes):

| Property   | Type                                   | Required | Description                                |
| ---------- | -------------------------------------- | -------- | ------------------------------------------ |
| `config`   | `IdCaptureConfig`                      | ❌       | Configuration for ID capture behavior      |
| `manager`  | `IdCaptureManager`                     | ❌       | Optional pre-built manager (advanced use)  |
| `onFinish` | `() => void`                           | ❌       | Called when capture completes successfully |
| `onError`  | `(error: string \| undefined) => void` | ❌       | Called when an error occurs                |

## WASM Requirements

The ID Capture module uses WebAssembly for document detection, blur/glare quality checks, and perspective correction. Pre-warm WASM during `setup()` so models are ready before the user reaches the camera step:

```typescript
await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
  wasm: { pipelines: ['idCapture'] },
});
```

See [WASM Configuration](/sdk-reference/web-sdk-2-wasm/) for self-hosted paths and the lower-level `warmupWasm()` API.

## Usage

### Vanilla HTML / TypeScript

```html
<incode-id></incode-id>

<script type="module">
  import { setup } from '@incodetech/core';
  import '@incodetech/web/id';
  import '@incodetech/web/id/styles.css';

  await setup({
    apiURL: 'https://demo-api.incodesmile.com',
    token: 'your-session-token',
    wasm: { pipelines: ['idCapture'] },
  });

  const id = document.querySelector('incode-id');
  id.config = {
    showTutorial: true,
    enableId: true,
    enablePassport: false,
    autoCaptureTimeout: 5,
    captureAttempts: 3,
  };
  id.onFinish = () => console.log('ID captured!');
  id.onError = (err) => console.error('ID error:', err);
</script>
```

### React

> **React 18 or earlier:** add the one-time JSX augmentation from [Framework Integration → TypeScript: JSX support for `incode-*` tags](/sdk-reference/web-sdk-2-framework-integration/#typescript-jsx-support-for-incode--tags). React 19+ doesn't need it, and can also use the simpler form from [Framework Integration → React 19+ shortcut](/sdk-reference/web-sdk-2-framework-integration/#react-19-shortcut).

```tsx
import { useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import type { IdCaptureConfig } from '@incodetech/core/id';
import '@incodetech/web/id';
import '@incodetech/web/id/styles.css';

type IdElement = HTMLElement & {
  config: IdCaptureConfig;
  onFinish: () => void;
  onError: (error: string | undefined) => void;
};

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
  wasm: { pipelines: ['idCapture'] },
});

export function IdCapture() {
  const ref = useRef<IdElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    el.config = {
      showTutorial: true,
      enableId: true,
      enablePassport: false,
      autoCaptureTimeout: 5,
      captureAttempts: 3,
    };
    el.onFinish = () => console.log('ID captured!');
    el.onError = (err) => console.error('ID error:', err);
  }, []);

  return <incode-id ref={ref} />;
}
```

For Angular (`CUSTOM_ELEMENTS_SCHEMA`) and Vue (`compilerOptions.isCustomElement`) setup, see [Framework Integration](/sdk-reference/web-sdk-2-framework-integration/).

---

## Workflow vs Flow

In a dashboard-driven Flow (`<incode-flow>` / `createOrchestratedFlowManager`), `ID` is a single step that captures the document **and** runs server-side processing (`/process/id`). This is what `<incode-id>` and `createIdCaptureManager` do by default.

In a server-driven Workflow (`<incode-workflow>` / `createWorkflowManager`), the workflow engine emits ID handling as **two distinct nodes**:

1. `ID_CAPTURE` — runs `idCaptureMachine` for the capture portion only, then advances without calling `/process/id`. Enabled by setting `IdCaptureConfig.skipProcessId = true` (the workflow engine sets this automatically on the `ID_CAPTURE` node's config).
2. `ID` — runs the headless `id-verification` module (`@incodetech/core/id-verification`, `createIdVerificationManager`) which invokes `/process/id` on its own. The orchestrator renders the corresponding `<incode-id-verification>` shell while the request is in flight. No UI work needed.

This split lets the workflow inject custom logic (manual review, retries, branching) between capture and verification. For a single-step capture+verify flow, keep using the standard `<incode-id>` / `createIdCaptureManager` path — `skipProcessId` defaults to `false`, so processing runs inline as before.

The `id-verification` module ships:

- `createIdVerificationManager` from `@incodetech/core/id-verification`: headless manager for the verification half (used by Workflow's `ID` node, also usable headlessly outside Workflow).
- `IdVerificationConfig`: config type for the manager.
- `<incode-id-verification>`: Preact UI shell registered automatically by the orchestrator; no public `@incodetech/web/id-verification` import path, since the orchestrator owns the mount.

---

## Headless Mode

For complete UI control, use the `createIdCaptureManager` from `@incodetech/core/id`.

### Quick Start

```tsx
import { setup } from '@incodetech/core';
import { createIdCaptureManager } from '@incodetech/core/id';
import { warmupWasm } from '@incodetech/core/wasm';

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
});

await warmupWasm({
  wasmPath: '/wasm/webLib.wasm',
  glueCodePath: '/wasm/webLib.js',
  modelsBasePath: '/wasm/models',
  pipelines: ['idCapture'],
});

const manager = createIdCaptureManager({
  config: {
    showTutorial: true,
    enableId: true,
    enablePassport: false,
    autoCaptureTimeout: 5,
    captureAttempts: 3,
  },
});

manager.subscribe((state) => {
  console.log('Status:', state.status);

  if (state.status === 'capture') {
    console.log('Mode:', state.currentMode); // 'front' or 'back'
    console.log('Detection:', state.detectionStatus);
    console.log('Counter:', state.counterValue); // Auto-capture countdown
  }

  if (state.status === 'finished') {
    console.log('ID captured successfully!');
    manager.stop();
  }
});

manager.load();
```

### State Machine Flow

```mermaid
flowchart LR
    idle -->|load| chooser
    chooser -->|selectDocument| tutorial
    tutorial -->|nextStep| permissions
    permissions -->|granted| capture
    capture -->|front done| frontFinished
    frontFinished --> capture
    capture -->|back done| processing
    processing --> finished
```

### States Reference

| Status             | Description                                                                                                                       | Key Properties                                                                  |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `idle`             | Initial state, waiting for `load()`                                                                                               | –                                                                               |
| `chooser`          | Document type selection                                                                                                           | `availableDocumentTypes`                                                        |
| `loading`          | Loading configuration                                                                                                             | –                                                                               |
| `tutorial`         | Showing tutorial                                                                                                                  | `selectedDocumentType`, `currentMode`                                           |
| `ageVerification`  | Regulation-required age-confirmation gate before capture. Reached when `config.ageAssurance === true`. Advance with `nextStep()`. | –                                                                               |
| `permissions`      | Camera permission handling                                                                                                        | `permissionStatus`                                                              |
| `capture`          | Active document capture                                                                                                           | `stream`, `currentMode`, `detectionStatus`, `counterValue`, `attemptsRemaining` |
| `frontFinished`    | Front side complete, transitioning to back                                                                                        | –                                                                               |
| `backFinished`     | Back side complete                                                                                                                | –                                                                               |
| `mandatoryConsent` | Regulation-required consent screen                                                                                                | `regulationType` (see [Mandatory Consent](#mandatory-consent-state-properties)) |
| `processing`       | Server-side document processing                                                                                                   | –                                                                               |
| `expired`          | Document expired after processing                                                                                                 | –                                                                               |
| `manualUpload`     | Manual file upload flow (see [Manual Upload](#manual-upload-state-properties))                                                    | full shape below                                                                |
| `digitalIdUpload`  | Digital ID upload sub-flow — PDF e-IDs uploaded by file picker (see [Digital ID Upload](#digital-id-upload-state-properties))     | full shape below                                                                |
| `finished`         | Capture complete                                                                                                                  | –                                                                               |
| `error`            | Fatal error occurred                                                                                                              | `error`                                                                         |
| `closed`           | User closed the flow                                                                                                              | –                                                                               |

### Capture State Properties

When `status === 'capture'`:

| Property             | Type                | Description                                                                                                                                                                                                                    |
| -------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `stream`             | `MediaStream`       | Camera stream for `<video>` element                                                                                                                                                                                            |
| `currentMode`        | `'front' \| 'back'` | Which side is being captured                                                                                                                                                                                                   |
| `captureStatus`      | `string`            | `'initializing'`, `'detecting'`, `'capturing'`, `'resultPreview'`, `'uploading'`, `'uploadError'`, `'success'`. `resultPreview` appears only when you opt in — see [Post-capture result preview](#post-capture-result-preview) |
| `detectionStatus`    | `string`            | Document detection feedback                                                                                                                                                                                                    |
| `counterValue`       | `number`            | Auto-capture countdown (seconds)                                                                                                                                                                                               |
| `attemptsRemaining`  | `number`            | Remaining capture attempts                                                                                                                                                                                                     |
| `uploadError`        | `string?`           | Error code if upload failed                                                                                                                                                                                                    |
| `uploadErrorMessage` | `string?`           | Human-readable error                                                                                                                                                                                                           |
| `needsBackCapture`   | `boolean`           | Whether back capture is needed                                                                                                                                                                                                 |
| `canRetry`           | `boolean`           | Whether retry is available                                                                                                                                                                                                     |
| `previewImageUrl`    | `string?`           | Captured image preview URL                                                                                                                                                                                                     |

### Mandatory Consent State Properties

When `status === 'mandatoryConsent'`:

| Property         | Type              | Description                                                                        |
| ---------------- | ----------------- | ---------------------------------------------------------------------------------- |
| `regulationType` | `RegulationTypes` | Which regional regulation triggered the consent screen. Used to pick consent copy. |

`RegulationTypes` is a string union:

```typescript
type RegulationTypes =
  | 'US'
  | 'Worldwide'
  | 'Other'
  | 'US_Illinois'
  | 'US_Texas'
  | 'US_California'
  | 'US_Washington';
```

`'Other'` is the fallback the SDK uses when the upload response doesn't pin a specific regulation. Drive your consent copy off this value.

Drive the screen with `acceptMandatoryConsent()` (proceed) or `cancelMandatoryConsent()` (abort the flow).

### Manual Upload State Properties

When `status === 'manualUpload'`. The user is uploading ID images by file picker — used as a fallback when live capture isn't possible, or when the backend forces it via configuration. The state has tab-aware UI hints (ID vs Passport), per-slot file metadata, and a continue gate.

| Property           | Type                                           | Description                                                                                                                                                                                                             |
| ------------------ | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `phase`            | `'selecting' \| 'uploading' \| 'exhausted'`    | `'selecting'` while the user picks files, `'uploading'` during upload, `'exhausted'` after retries are spent.                                                                                                           |
| `uploadingSide`    | `'front' \| 'back' \| 'passport' \| undefined` | Which side is currently uploading; `undefined` outside `phase === 'uploading'`.                                                                                                                                         |
| `activeTab`        | `'id' \| 'passport'`                           | Currently selected tab. Use `manualUploadChangeTab()` to switch.                                                                                                                                                        |
| `showIdTab`        | `boolean`                                      | Whether the ID tab should be rendered (driven by config).                                                                                                                                                               |
| `showPassportTab`  | `boolean`                                      | Whether the Passport tab should be rendered.                                                                                                                                                                            |
| `showBackSlot`     | `boolean`                                      | On the ID tab, whether to render the back-of-ID slot. The server controls visibility per document — the back slot is hidden when the front upload response sets `skipBackIdCapture` (e.g. passports, single-sided IDs). |
| `frontFileName`    | `string \| undefined`                          | File name the user picked for the front of the ID, if any.                                                                                                                                                              |
| `backFileName`     | `string \| undefined`                          | File name for the back of the ID, if any.                                                                                                                                                                               |
| `passportFileName` | `string \| undefined`                          | File name for the passport upload, if any.                                                                                                                                                                              |
| `frontUploaded`    | `boolean`                                      | True once the front file has been accepted server-side.                                                                                                                                                                 |
| `backUploaded`     | `boolean`                                      | True once the back file has been accepted server-side.                                                                                                                                                                  |
| `passportUploaded` | `boolean`                                      | True once the passport file has been accepted server-side.                                                                                                                                                              |
| `canContinue`      | `boolean`                                      | Whether the Continue button should be enabled. Use this to gate `manualUploadContinue()`.                                                                                                                               |
| `retriesLeft`      | `number`                                       | Remaining retries on the current tab.                                                                                                                                                                                   |
| `errorKey`         | `string \| null`                               | i18n key for the current error message. Look it up via your translation table; `null` when there is no error.                                                                                                           |

Drive the screen with `manualUploadSelectFile(side, file)`, `manualUploadChangeTab(tab)`, and `manualUploadContinue()`. Call `manualUploadReset()` to clear all selections and start the tab over.

### Digital ID Upload State Properties

When `status === 'digitalIdUpload'`. Reached when the backend allows uploading a digital ID (typically an EU eID or similar government-issued PDF) instead of capturing a physical document. The sub-flow walks the user through a tutorial → file picker → preview → upload → success/failure cycle with a bounded retry budget (3 attempts).

| Property            | Type                              | Description                                                                                                                                                                                                                                 |
| ------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `phase`             | union                             | `'tutorial'`, `'selecting'`, `'reviewing'`, `'uploading'`, `'holding'`, `'success'`, `'error'`, `'fileTooLarge'`, `'exhausted'`. `'holding'` is a brief stabilization step shown while the upload finishes; treat as `'uploading'` UI-wise. |
| `file`              | `File \| null`                    | The PDF the user picked (`null` outside `'reviewing'` / `'uploading'` / `'holding'`).                                                                                                                                                       |
| `fileName`          | `string \| undefined`             | The file's display name. Use this to render the preview row.                                                                                                                                                                                |
| `failReason`        | `DigitalUploadFailReason \| null` | Set when `phase === 'error'`. Union: `'DIGITAL_ID_REQUESTED_BUT_OTHER_PROVIDED'`, `'ID_TYPE_UNACCEPTABLE'`, `'FILE_CHANGED_ERROR'`, `'INVALID_FILE_TYPE'`, `'NETWORK_ERROR'`, `'GENERIC'`.                                                  |
| `attemptsRemaining` | `number`                          | Remaining upload retries (starts at 3). Hits 0 → `phase === 'exhausted'`.                                                                                                                                                                   |
| `uploadProgress`    | `number`                          | Upload progress 0–100. Only meaningful in `'uploading'` / `'holding'`.                                                                                                                                                                      |
| `pickerRequestId`   | `number`                          | Monotonic counter — increments each time the SDK wants the host UI to open the file picker. Watch for changes and trigger your `<input type="file">` programmatically (the SDK never opens it itself).                                      |

**Accepted files.** PDF only (`application/pdf`), 5 MB maximum.

**Phase progression.** `tutorial` → `selecting` (host opens picker on each `pickerRequestId` change) → `reviewing` (user confirms or replaces) → `uploading` → `success` (auto-advances out of the sub-flow) or `error` → back to `selecting`, until `attemptsRemaining === 0` → `exhausted`. `fileTooLarge` is shown immediately when the user picks an oversized file and does not consume a retry.

Drive the screen with `digitalUploadNextStep()`, `digitalUploadPickFile(file)`, `digitalUploadConfirm()`, `digitalUploadReplace()`, `digitalUploadRetry()`, `digitalUploadChooseAnother()`, and `digitalUploadScanInstead()` (falls back to live capture when allowed).

### Detection Status Values

| Status          | User Instruction                |
| --------------- | ------------------------------- |
| `idle`          | "Preparing camera..."           |
| `idNotDetected` | "Position your ID in the frame" |
| `detecting`     | "Hold steady..."                |
| `farAway`       | "Move closer"                   |
| `blur`          | "Hold still, image is blurry"   |
| `glare`         | "Adjust angle to reduce glare"  |
| `wrongSide`     | "Please show the other side"    |
| `capturing`     | "Capturing..."                  |
| `manualCapture` | "Tap to capture"                |
| `offline`       | "No network connection"         |

### API Methods

| Method                               | Description                                                       | When to Use                                                  |
| ------------------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------ |
| `load()`                             | Starts the ID capture flow                                        | Always call first                                            |
| `selectDocument(type)`               | Selects `'id'` or `'passport'`                                    | When `chooser`                                               |
| `nextStep()`                         | Advances from tutorial to permissions                             | When `tutorial`                                              |
| `requestPermission()`                | Requests camera access                                            | When `permissions.idle`                                      |
| `goToLearnMore()`                    | Shows permission help                                             | When `permissions.idle`                                      |
| `back()`                             | Goes back from learn more                                         | When `permissions.learnMore`                                 |
| `capture()`                          | Manual capture trigger                                            | When `detectionStatus === 'manualCapture'`                   |
| `switchToManualCapture()`            | Switch from auto to manual mode                                   | During auto-capture                                          |
| `retryCapture()`                     | Retry after upload error                                          | When `canRetry` is true                                      |
| `continueFromError()`                | Continue after non-fatal error                                    | When error allows continuation                               |
| `completeResultPreview()`            | Ends the post-capture preview beat and starts uploading           | When `captureStatus === 'resultPreview'`                     |
| `continueToBack()`                   | Proceed to back capture                                           | When `frontFinished`                                         |
| `continueToFront()`                  | Flip back to front capture                                        | When `backFinished`                                          |
| `skipBack()`                         | Skip back capture                                                 | When back is optional                                        |
| `acceptMandatoryConsent()`           | Accept the regulation-required consent                            | When `mandatoryConsent`                                      |
| `cancelMandatoryConsent()`           | Decline the regulation-required consent                           | When `mandatoryConsent`                                      |
| `manualUploadSelectFile(side, file)` | Pick a file for the manual upload fallback                        | When `manualUpload`, `side: 'front' \| 'back' \| 'passport'` |
| `manualUploadChangeTab(tab)`         | Switch between ID and Passport upload tabs                        | When `manualUpload` and both tabs are shown                  |
| `manualUploadContinue()`             | Submit the selected manual-upload files                           | When `manualUpload` and `state.canContinue` is true          |
| `manualUploadReset()`                | Clear all selected files on the active tab and start over         | When `manualUpload`                                          |
| `digitalUploadNextStep()`            | Advance from the digital-upload tutorial to the picker            | When `digitalIdUpload` and `phase === 'tutorial'`            |
| `digitalUploadPickFile(file)`        | Hand a picked PDF to the SDK for review                           | When `digitalIdUpload` and `phase === 'selecting'`           |
| `digitalUploadConfirm()`             | Confirm the previewed file and start uploading                    | When `digitalIdUpload` and `phase === 'reviewing'`           |
| `digitalUploadReplace()`             | Discard the previewed file and re-open the picker                 | When `digitalIdUpload` and `phase === 'reviewing'`           |
| `digitalUploadRetry()`               | Retry after an upload error (consumes one of `attemptsRemaining`) | When `digitalIdUpload` and `phase === 'error'`               |
| `digitalUploadChooseAnother()`       | Pick a different file after `fileTooLarge`                        | When `digitalIdUpload` and `phase === 'fileTooLarge'`        |
| `digitalUploadScanInstead()`         | Abandon digital upload and fall back to live capture              | When `digitalIdUpload` (any phase, if allowed by config)     |
| `updateDetectionArea(area)`          | Update detection bounds                                           | On resize/orientation change                                 |
| `close()`                            | Close the flow                                                    | Anytime                                                      |
| `reset()`                            | Reset to initial state                                            | After `finished` or `error`                                  |
| `stop()`                             | Cleanup resources                                                 | When unmounting                                              |
| `getState()`                         | Returns current state                                             | Anytime                                                      |
| `subscribe(callback)`                | Subscribe to state changes                                        | Returns unsubscribe function                                 |

### React Example

```tsx
import { useState, useEffect, useRef } from 'react';
import {
  createIdCaptureManager,
  type IdCaptureState,
} from '@incodetech/core/id';

function CustomIdCapture() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [manager] = useState(() =>
    createIdCaptureManager({
      config: { enableId: true, showTutorial: false, autoCaptureTimeout: 5 },
    }),
  );
  const [state, setState] = useState<IdCaptureState>({ status: 'idle' });

  useEffect(() => {
    const unsubscribe = manager.subscribe(setState);
    manager.load();
    return () => {
      unsubscribe();
      manager.stop();
    };
  }, [manager]);

  useEffect(() => {
    if (state.status === 'capture' && state.stream && videoRef.current) {
      videoRef.current.srcObject = state.stream;
    }
  }, [state]);

  switch (state.status) {
    case 'chooser':
      return (
        <div>
          <button onClick={() => manager.selectDocument('id')}>ID Card</button>
          <button onClick={() => manager.selectDocument('passport')}>
            Passport
          </button>
        </div>
      );

    case 'permissions':
      return (
        <div>
          <p>Camera access is required</p>
          <button onClick={() => manager.requestPermission()}>
            Allow Camera
          </button>
        </div>
      );

    case 'capture':
      return (
        <div>
          <video ref={videoRef} autoPlay playsInline muted />
          <p>Capturing: {state.currentMode} side</p>
          <p>{state.detectionStatus}</p>
          {state.captureStatus === 'detecting' && (
            <p>Auto-capture in: {state.counterValue}s</p>
          )}
          {state.detectionStatus === 'manualCapture' && (
            <button onClick={() => manager.capture()}>Capture</button>
          )}
          {state.captureStatus === 'uploadError' && state.canRetry && (
            <button onClick={() => manager.retryCapture()}>Retry</button>
          )}
        </div>
      );

    case 'finished':
      return <div>✅ ID captured successfully!</div>;

    case 'error':
      return <div>Error: {state.error}</div>;

    default:
      return <div>Loading...</div>;
  }
}
```

### Capture-only flow

`createIdCaptureOnlyManager` exposes the same state machine and API surface as `createIdCaptureManager`, but bypasses the Incode upload pipeline. Instead of submitting the captured frames to Incode and waiting on server-side processing, the manager invokes a customer-supplied `onCapture(response)` callback with the captured images and reaches `finished` locally. Use this when you want to capture in the browser but upload (or process) the bytes through your own pipeline.

The config is `IdCaptureConfig` plus a required `onCapture` callback — captured at compile time, so a missing callback is a type error rather than a silent runtime no-op:

```typescript
import { setup } from '@incodetech/core';
import { initializeSession } from '@incodetech/core/session';
import {
  createIdCaptureOnlyManager,
  type IdCaptureOnlyConfig,
  type CaptureOnlyResponse,
} from '@incodetech/core/id';

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  wasm: { pipelines: ['idCapture'] },
});
await initializeSession({ token: 'your-session-token' });

const config: IdCaptureOnlyConfig = {
  showTutorial: true,
  enableId: true,
  enablePassport: false,
  autoCaptureTimeout: 5,
  captureAttempts: 3,
  // ...the rest of IdCaptureConfig
  onCapture: async (response: CaptureOnlyResponse) => {
    // response.frontImage: IdCapturedImageData
    // response.backImage: IdCapturedImageData | undefined
    await uploadToMyBackend(response.frontImage.blob, response.backImage?.blob);
  },
};

const manager = createIdCaptureOnlyManager({ config });
manager.subscribe((state) => {
  if (state.status === 'finished') manager.stop();
});
manager.load();
```

The `CaptureOnlyResponse` payload is:

```typescript
type CaptureOnlyResponse = {
  frontImage: IdCapturedImageData;
  backImage: IdCapturedImageData | undefined;
};

type IdCapturedImageData = {
  imageBase64: string; // Always present — the unprocessed full frame
  blob: Blob; // Same content as Blob
  url: string; // Object-URL for direct rendering
  metadata: string; // Capture metadata (serialized)
  croppedImage?: {
    // Best-effort document crop (see caveat below)
    imageBase64: string;
    blob: Blob;
    url: string;
  };
};
```

> **About `croppedImage`.** This is a _best-effort_ approximate crop produced by a bilinear quad mapping — **not** a true perspective/homography warp. It is populated only when on-device quad detection succeeds, and it will visibly diverge from the correct projective result for strongly tilted captures. Prefer `imageBase64` (the unprocessed full frame) when fidelity matters; treat `croppedImage` as a preview hint.

`createIdCaptureOnlyManagerFromActor` is also exported for advanced cases where you supply a pre-built XState actor (e.g. to swap services in tests).

---

## Configuration Options

`IdCaptureConfig` is `TutorialIdConfig & { ...extras }` — `TutorialIdConfig` is the dashboard-driven shape (almost all fields are required), and the extras are advanced overrides.

> **Orchestrated vs headless:** when `<incode-id>` runs inside `<incode-flow>` (or `createOrchestratedFlowManager`), the orchestrator passes the full config from the dashboard automatically. The required marks below apply when you instantiate the module yourself via `createIdCaptureManager({ config })` or set `<incode-id>.config` directly.

### Document selection

| Option                      | Type      | Required | Description                                   |
| --------------------------- | --------- | -------- | --------------------------------------------- |
| `enableId`                  | `boolean` | ✅       | Enable ID-card capture                        |
| `enablePassport`            | `boolean` | ✅       | Enable passport capture                       |
| `deviceWallet`              | `boolean` | ✅       | Enable device-wallet (digital ID) flow        |
| `digitalIdsUpload`          | `boolean` | ✅       | Allow uploading digital IDs as an alternative |
| `manualUploadIdCapture`     | `boolean` | ✅       | Enable the manual file-upload fallback path   |
| `showDocumentChooserScreen` | `boolean` | ✅       | Render the document-type chooser screen       |

### Capture behavior

| Option                    | Type      | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `showTutorial`            | `boolean` | ✅        | Show tutorial before capture                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `onlyFront`               | `boolean` | ✅        | Declares that the document is one-sided (like a passport). Forwarded to the front-upload endpoint as the [`onlyFront`](/api-reference/add-front-id-v2/) query param. **Not a client-side override:** the server weighs it against its own classification of the uploaded image and answers with `skipBackIdCapture`, so a genuinely two-sided document will still ask for the back — passports included, since they are not always single-sided. For guaranteed front-only capture, configure it on the dashboard. Deliberately not symmetric with `onlyBack`, which is honored client-side. |
| `onlyBack`                | `boolean` | ✅        | Capture the back side only                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `barcodeCapture`          | `boolean` | ✅        | Use barcode scanning when applicable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `fetchAdditionalPage`     | `boolean` | ✅        | Capture an additional page after the main scan                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `autoCaptureTimeout`      | `number`  | ✅        | Seconds before auto-capture triggers                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `deviceIdleTimeout`       | `number`  | ✅        | Seconds of device idleness before timing out                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `captureAttempts`         | `number`  | ✅        | Maximum retry attempts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `enableIdRecording`       | `boolean` | ✅        | Enable video recording of the capture                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `usSmartCapture`          | `boolean` | ✅        | US-specific smart-capture mode                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `secondId`                | `boolean` | ✅        | Capture a second ID document after the first                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `showCaptureButtonInAuto` | `boolean` | ❌        | Show a manual capture button during auto-capture                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `alwaysCaptureBackOfId`   | `boolean` | ❌        | Always capture the back of the ID, even when the front response would normally skip it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `extractIdFace`           | `boolean` | ❌        | Set `false` to skip biometric face extraction from the ID: no biometric template is created for the session. Use it in jurisdictions with biometric-consent restrictions. Face match is then unavailable for that session. Omit or set `true` to keep the default behavior.                                                                                                                                                                                                                                                                                                                                              |

### Per-country / per-document overrides

| Option                      | Type   | Required | Description                                                                                                                                                                                                                                             |
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `perCountryPerDocOverrides` | object | ✅        | Nested map keyed by country code → document type → `{ onlyFront, onlyBack, fetchAdditionalPage }`. **Not read by the SDK** — per-document capture policy is resolved server-side and returned as `skipBackIdCapture` on the upload response. Pass `{}`. |

### Extras (from `IdCaptureConfig` beyond `TutorialIdConfig`)

| Option                         | Type      | Required | Description                                                                                                     |
| ------------------------------ | --------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| `ageAssurance`                 | `boolean` | ❌        | Enable age assurance features                                                                                   |
| `mergeSessionRecordings`       | `boolean` | ❌        | Merge per-step recordings into a single session-level recording                                                 |
| `isDeepsightEnabled`           | `boolean` | ❌        | Override Deepsight enablement (otherwise driven by the session)                                                 |
| `supportsCaptureResultPreview` | `boolean` | ❌        | Opt into the post-capture preview beat — see [Post-capture result preview](#post-capture-result-preview) below. |

### Post-capture result preview

The built-in `<incode-id>` component shows a brief frozen preview of the frame it just captured before uploading it. Headless integrations skip that beat by default and go straight from capture to upload.

To render your own version of it, create the manager with `supportsCaptureResultPreview: true`. `captureStatus` then reaches `resultPreview` after capture, carrying the captured frame on `previewImageUrl`, and waits there until you call `completeResultPreview()`. Note that `nextStep()` does not advance out of it.

```ts
const manager = createIdCaptureManager({
  config: { ...idConfig, supportsCaptureResultPreview: true },
});

manager.subscribe((state) => {
  if (state.status === 'capture' && state.captureStatus === 'resultPreview') {
    showPreview(state.previewImageUrl, () => manager.completeResultPreview());
  }
});
```

Opt in only when your UI handles the state and calls `completeResultPreview()` — enabling it otherwise leaves capture waiting. Your integration should still handle the direct capture-to-upload path, because the preview does not appear on every session. Pass `false` to opt out explicitly. See [Headless Mode](/sdk-reference/web-sdk-2-headless-mode/#optional-result-preview-capturestatus-resultpreview) for the full walkthrough.

`IdCaptureConfig` also exposes a few advanced fields reserved for Incode-internal tooling. They aren't part of the public integration surface and aren't documented here.

## Error Codes

| Code                               | Description                                 | User Action               |
| ---------------------------------- | ------------------------------------------- | ------------------------- |
| `UPLOAD_ERROR`                     | Upload failed                               | Retry capture             |
| `CLASSIFICATION_FAILED`            | Document not recognized                     | Use clearer image         |
| `LOW_SHARPNESS`                    | Image too blurry                            | Hold device steadier      |
| `GLARE_DETECTED`                   | Glare on document                           | Adjust lighting           |
| `WRONG_DOCUMENT_SIDE`              | Wrong side shown                            | Flip document             |
| `ID_TYPE_UNACCEPTABLE`             | Document not supported                      | Use different ID          |
| `READABILITY_ISSUE`                | Cannot read document fields                 | Improve lighting or angle |
| `RETRY_EXHAUSTED_CONTINUE_TO_BACK` | Front retries exhausted; proceeding to back | Auto-advances             |
| `RETRY_EXHAUSTED_SKIP_BACK`        | Back retries exhausted; skipping back       | Auto-advances             |
| `NO_MORE_TRIES`                    | Max attempts reached                        | Flow ends                 |
| `UNEXPECTED_ERROR`                 | Unexpected internal error                   | Retry or contact support  |
| `NO_TOKEN`                         | Session token missing                       | Re-initialize SDK         |
| `PERMISSION_DENIED`                | Camera access denied                        | Enable in settings        |
| `USER_CANCELLED`                   | User cancelled the flow                     | Re-open module            |
| `SERVER_ERROR`                     | Server-side error                           | Retry later               |

## Examples

Each example shows a config object you can assign to `<incode-id>.config` (or pass through your framework's property binding). Plug it into the React or vanilla pattern shown in [Usage](#usage).

### ID Card Only

```ts
const config: IdCaptureConfig = {
  enableId: true,
  enablePassport: false,
  showTutorial: true,
  autoCaptureTimeout: 5,
};
```

### Passport Only

```ts
const config: IdCaptureConfig = {
  enableId: false,
  enablePassport: true,
  showTutorial: false,
};
```

---

## See Also

- [Headless Mode](/sdk-reference/web-sdk-2-headless-mode/): Complete headless API reference
- [WASM Configuration](/sdk-reference/web-sdk-2-wasm/): Setting up WebAssembly
- [Individual Modules](/sdk-reference/web-sdk-2-individual-modules/): Overview of all modules