> ## 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 consent for BIPA, CUBI and Colorado

> Explicit biometric consent before the selfie for Illinois, Texas and Colorado: the triggers, native SDK 4.10.0 and the decision fields.

Illinois (BIPA), Texas (CUBI) and Colorado require a notice and an explicit release before a business collects face geometry, from a selfie or from the photo on an ID. Didit can show a consent step before the first face capture to the people those laws cover, and keeps the answer on the session as evidence.

This page explains when the step appears, which apps can show it, and how the answer reaches your decision and webhooks.

> [!WARNING]
>
> Didit shows the step and records the answer. You decide whether your user journey meets the biometric laws that apply to your business. This page is not legal advice: review your implementation with your counsel.

## When the step appears

A session shows the consent step when both of these are true:

1. **The workflow captures a face.** A Liveness, Face Match or Age Estimation step, the front-camera selfie the ID Verification step takes (unless you turned it off), or full-session video recording.
2. **The application's consent mode asks for it.** In **Auto** mode, one of the [triggers](#what-triggers-the-step-in-auto-mode) below applies. In **Always** mode, every session that captures a face asks.

The step appears before the first face capture. When only the ID document shows a covered state, it appears right after the ID capture and before the selfie, and no face geometry is computed from the ID capture images until the person agrees.

Didit checks the requirement when the person opens the flow and again at each step until it applies. Once a session requires consent, it keeps requiring it.

### Consent modes

Choose the mode in the console, under the application's **Settings > Data and privacy > Biometric consent**. Each change is recorded in the [audit log](https://docs.didit.me/console/audit-logs.md).

| Mode | What happens |
| - | - |
| **Auto** (default, recommended) | Shows the step only where a [trigger](#what-triggers-the-step-in-auto-mode) applies. Everyone else sees the standard one-line notice. |
| **Always** | Every person whose face is captured sees the step, wherever they are. The decision records `trigger: "forced"`. |
| **Never** | Didit never shows the step. Where state law requires consent, you collect it yourself before the selfie and keep the record. |

### What triggers the step in Auto mode

Didit checks these rules in order and records the first one that applies in `biometric_consent.trigger`:

| Trigger | When it applies |
| - | - |
| `declared_state` | You declared a covered state when creating the session: `user_state`, or a US address in `expected_details`. |
| `ip_state` | The person's IP is located in Illinois, Texas or Colorado. |
| `ip_unknown` | The person looks to be in the US (a US IP, a US document, or US details you sent) but their state cannot be told: the IP is in the US with no state, or cannot be located while your details or the document place the person in the US. |
| `document_issuer` | The identity document read at ID capture was issued by a covered state. |

**VPNs, proxies and hosting networks do not ask on their own.** Didit locates the person's IP like any other: one in Illinois, Texas or Colorado shows the step, one anywhere else does not. The step is asked only on clear evidence of a covered state (the IP, the state that issued the ID, or a state you declared), or when the person is placed in the US without a state (`ip_unknown`). A person in a covered state behind a VPN that exits elsewhere, with an ID no covered state issued, is not asked; use **Always** mode, or send `user_state`, if you need to cover that case.

Two more values describe how a session's consent was settled rather than why it was asked:

| Trigger | Meaning |
| - | - |
| `forced` | The application's mode is **Always**. |
| `customer_collected` | You sent consent you had already collected when creating the session (see below). |

## Tell Didit what you already know

[Create Session](https://docs.didit.me/sessions-api/create-session.md) takes two optional fields for this:

| Field | What it does |
| - | - |
| `user_state` | Where the person lives, when you know it: an ISO 3166-2 subdivision code (`US-IL`, `ES-M`) or a US state code or name (`IL`, `Illinois`). A covered state shows the step with `trigger: "declared_state"`. An unknown value returns `400`. |
| `biometric_consent` | Consent you already collected: `{ "given_at": "<ISO 8601>", "text_version": "<your version id>" }`. The flow does not ask again, and the decision records `status: "given"` with `trigger: "customer_collected"`. `given_at` cannot be in the future. |

```json theme={null}
{
  "workflow_id": "YOUR_WORKFLOW_ID",
  "vendor_data": "user-42",
  "user_state": "US-IL",
  "biometric_consent": {
    "given_at": "2026-10-13T09:12:00Z",
    "text_version": "consent-v3"
  }
}
```

## Which apps show the step

The step needs an app that can display it. Didit decides this on every request the app makes:

| Where the person verifies | Shows the step |
| - | - |
| The hosted verification flow: a browser, the [Web SDK](https://docs.didit.me/integration/web-sdks/overview.md), or a WebView | Yes |
| [Native SDK](https://docs.didit.me/integration/native-sdks/overview.md) 4.10.0 or later (iOS, Android, and React Native or Flutter releases that bundle native 4.10.0 or later) | Yes |
| A native SDK below 4.10.0, or one that does not report its version | No |

### Native SDKs below 4.10.0

Native SDKs released before 4.10.0 do not know the consent step, so Didit never asks them for it. The verification runs exactly as it did before: the selfie and the other face captures are accepted and processed, and the session reaches its decision as usual.

Didit never records consent that was not given. When the session required consent and an older SDK captured a face, the decision shows:

```json theme={null}
"biometric_consent": {
  "required": true,
  "trigger": "ip_state",
  "status": "pending",
  "given_at": null,
  "reason": "BIOMETRIC_CONSENT_UNSUPPORTED_SDK"
}
```

> [!WARNING]
>
> **Upgrade to native SDK 4.10.0 or later if you verify people in Illinois, Texas or Colorado in a native app.** Until you do, those sessions finish without explicit consent, and collecting it before the selfie is your responsibility.

If the person continues the same session in the hosted flow (on another device, or in a WebView the app opens for some liveness types), the hosted flow shows the step and captures no face there until they answer. Consent given there keeps `reason: "BIOMETRIC_CONSENT_UNSUPPORTED_SDK"`, because faces were already captured without it.

## What the person sees

The step tells the person what is collected, why, who has it and how long it is kept, and links to the [Biometric data policy](https://didit.me/legal/biometric-data-policy). It has one button: **Agree and continue**, which stays disabled until the person ticks the agreement box.

Didit keeps the answer as evidence: when it was given, the version and hash of the text shown, the language, the workflow step, and the IP, location and user agent of the request. Once the person agrees, the answer is final.

## When the person does not agree

The step has no decline button. A person who does not want to agree leaves the flow: no face is captured, and the session ends as `Abandoned` like any other session the person does not finish (see [Verification statuses](https://docs.didit.me/integration/verification-statuses.md)). Offer them another way to verify.

### Declined consent in the API

The decision keeps a `declined` status and the `BIOMETRIC_CONSENT_DECLINED` reason as API values. No Didit screen offers a decline today, so the hosted flow and the native SDKs do not record one. A session that records a decline:

* Ends as `Declined`, and the `status.updated` webhook fires as for any other decline.
* Shows `biometric_consent.status` as `declined`, `reason` and `decline_reason` as `BIOMETRIC_CONSENT_DECLINED`, and `declined_at` with the time.
* Captures no face after the decline. Every face image and face template the session already holds is deleted: selfies, the ID capture's front-camera selfie, the face crop of the document portrait, liveness and full-session videos. `face_data_deleted_at` records when. The decision no longer returns them, and the deletion is irreversible.
* Keeps the document images and the data read from them: the decline does not affect them.

## In the decision and webhooks

Every User Verification decision carries a `biometric_consent` object, in [Retrieve Session](https://docs.didit.me/sessions-api/retrieve-session.md) and in the `decision` of [session webhooks](https://docs.didit.me/integration/webhooks.md). It is `null` when the session was never evaluated, for example a workflow without a face capture.

| `status` | Meaning |
| - | - |
| `not_required` | The session did not require consent. |
| `pending` | Consent is required and was not given. Read `reason` to tell an older SDK from a person who has not answered yet. |
| `given` | The person agreed in the flow, or you sent `biometric_consent` at creation. |
| `declined` | A decline was recorded, and the session ended as `Declined`. No Didit screen offers a decline today (see [above](#declined-consent-in-the-api)). |

| `reason` | Meaning |
| - | - |
| `BIOMETRIC_CONSENT_UNSUPPORTED_SDK` | A native SDK below 4.10.0, or one that did not report its version, captured a face without the step. |
| `BIOMETRIC_CONSENT_DECLINED` | A decline was recorded. No Didit screen offers a decline today. |
| `null` | Nothing to explain. Always `null` when the session did not require consent. |

A session where the person agreed in the hosted flow:

```json theme={null}
"biometric_consent": {
  "required": true,
  "trigger": "declared_state",
  "status": "given",
  "given_at": "2026-10-13T18:31:07.412000Z",
  "declined_at": null,
  "text_version": "bio-consent-2026-10-12",
  "locale": "en",
  "policy_url": "https://didit.me/legal/biometric-data-policy",
  "step": "LIVENESS",
  "ip_address": "198.51.100.23",
  "ip_country_code": "ES",
  "detected_ip_address": "198.51.100.23",
  "detected_country_code": "ES",
  "jurisdictions": ["IL"],
  "retention_months": 36,
  "reason": null,
  "decline_reason": null,
  "face_data_deleted_at": null
}
```

The example is shortened. Every field, with its type, is in [Data models](https://docs.didit.me/reference/data-models.md#biometric-consent).

## Related

* [Create Session](https://docs.didit.me/sessions-api/create-session.md): `user_state` and `biometric_consent`.
* [Retrieve Session](https://docs.didit.me/sessions-api/retrieve-session.md): the decision that carries `biometric_consent`.
* [Native SDKs](https://docs.didit.me/integration/native-sdks/overview.md): versions and upgrade steps.
* [Security and compliance](https://docs.didit.me/getting-started/security-compliance.md): your responsibilities in verification flows.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.