> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.withverifa.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withverifa.com/_mcp/server.

# Web Capture Flow

> Integrate Verifa's hosted capture experience into your web application.

The web capture flow is Verifa's hosted verification experience. You create a
session, redirect the user to the capture URL, and Verifa handles document
scanning, selfie collection, liveness detection, and consent collection.

## How it works

```
Your Server                    Verifa                         User
    │                            │                              │
    │  POST /api/v1/sessions     │                              │
    │───────────────────────────▶│                              │
    │  { capture_url }           │                              │
    │◀───────────────────────────│                              │
    │                            │                              │
    │  redirect to capture_url   │                              │
    │─────────────────────────────────────────────────────────▶│
    │                            │     consent + documents      │
    │                            │◀─────────────────────────────│
    │                            │     selfie + liveness        │
    │                            │◀─────────────────────────────│
    │                            │                              │
    │                            │  redirect to redirect_url    │
    │                            │─────────────────────────────▶│
    │                            │                              │
    │  webhook: session.approved │                              │
    │◀───────────────────────────│                              │
```

## Step 1: Create a session

#### cURL

```bash
curl -X POST https://api.withverifa.com/api/v1/sessions \
  -H "X-API-Key: vk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "external_ref": "user_abc123",
    "country": "US",
    "redirect_url": "https://your-app.com/verify/complete"
  }'
```

#### Python

```python
import requests

response = requests.post(
    "https://api.withverifa.com/api/v1/sessions",
    headers={"X-API-Key": "vk_live_your_key_here"},
    json={
        "external_ref": "user_abc123",
        "country": "US",
        "redirect_url": "https://your-app.com/verify/complete",
    }
)
session = response.json()
capture_url = session["capture_url"]
```

#### JavaScript

```javascript
const response = await fetch('https://api.withverifa.com/api/v1/sessions', {
  method: 'POST',
  headers: {
    'X-API-Key': 'vk_live_your_key_here',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    external_ref: 'user_abc123',
    country: 'US',
    redirect_url: 'https://your-app.com/verify/complete',
  }),
});
const session = await response.json();
const captureUrl = session.capture_url;
```

#### Go

```go
body, _ := json.Marshal(map[string]string{
    "external_ref": "user_abc123",
    "country":      "US",
    "redirect_url": "https://your-app.com/verify/complete",
})
req, _ := http.NewRequest("POST", "https://api.withverifa.com/api/v1/sessions", bytes.NewBuffer(body))
req.Header.Set("X-API-Key", "vk_live_your_key_here")
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var session map[string]interface{}
json.NewDecoder(resp.Body).Decode(&session)
captureURL := session["capture_url"].(string)
```

```json
{
  "id": "session_abc123",
  "status": "pending",
  "capture_url": "https://app.withverifa.com/verify/tok_abc123...",
  "qr_code_data_url": "data:image/png;base64,...",
  "is_sandbox": false,
  "created_at": "2026-02-01T12:00:00Z",
  "expires_at": "2026-02-01T12:20:00Z"
}
```

### Key parameters

| Parameter      | Description                                                              |
| -------------- | ------------------------------------------------------------------------ |
| `external_ref` | Your internal user ID. Links the session to an identity record.          |
| `country`      | ISO country code. Scopes document type options and geo-dependent checks. |
| `redirect_url` | Where to send the user after capture completes.                          |
| `workflow_id`  | Specific workflow to use (defaults to your org's default workflow).      |
| `metadata`     | Arbitrary key-value data stored with the session.                        |

## Step 2: Send the user to capture

### Option A: Full-page redirect

The simplest approach. Redirect the user to the `capture_url`:

```javascript
// After creating a session on your server
window.location.href = session.capture_url;
```

After the user completes the flow, they are redirected to your `redirect_url`
with query parameters:

```
https://your-app.com/verify/complete?session_id=session_abc123&status=completed
```

### Option B: Iframe embed

Embed the capture flow in your page:

```html
<iframe
  id="verifa-capture"
  src="https://app.withverifa.com/verify/tok_abc123..."
  allow="camera; microphone"
  style="width: 100%; height: 700px; border: none;"
></iframe>
```

Listen for completion via `postMessage`:

```javascript
window.addEventListener("message", (event) => {
  if (event.origin !== "https://app.withverifa.com") return;

  const { type, session_id, status } = event.data;

  if (type === "verifa:complete") {
    console.log(`Session ${session_id} completed with status: ${status}`);
    // Remove iframe and show success state
  }

  if (type === "verifa:error") {
    console.error("Capture error:", event.data.error);
  }
});
```

### Option C: QR code

Display the `qr_code_data_url` so mobile users can scan and complete
verification on their phone:

```html
<img src="${session.qr_code_data_url}" alt="Scan to verify" />
<p>Scan with your phone camera to complete verification</p>
```

This is useful for desktop-first flows where the user's phone has a better
camera.

## Step 3: Capture flow screens

The capture flow guides the user through these screens (order depends on your
workflow's `data_requirements`; document-free workflows —
`data_requirements.document.enabled: false` — skip the document screens
entirely and go straight from personal information to the selfie):

### 1. Consent

Biometric processing consent. Records timestamp, IP address, and consent
version. Required before any data collection.

### 2. Document type selection

The user selects their document type:

* Passport
* Driver's license
* National ID card
* Residence permit

Available types are scoped by the `country` parameter.

### 3. Document upload

Front side (required) and back side (if applicable). The capture UI provides:

* Real-time camera viewfinder with alignment guides
* Auto-capture when a document is detected
* Manual photo option as fallback
* Image quality feedback (blur, glare, resolution)

Documents are encrypted with AES-256-GCM immediately on upload. Maximum file
size: 10 MB.

### 4. Selfie

Live selfie capture with alignment guide. The selfie is compared against the ID
photo for face matching.

### 5. Liveness challenge

Three randomized challenges from: blink, smile, turn left, turn right, look up,
nod, open mouth. Challenges are cryptographically bound to the session via a
server-generated nonce to prevent replay attacks.

Workflows that contain the **Returning User Face Verification** policy always run
the liveness challenge, even if the workflow's liveness setting is turned off —
re-verifying a returning user requires a live selfie.

### 6. Additional data (optional)

If your workflow requires it:

* **Email and phone** — For risk scoring and OTP verification
* **Address** — For address validation checks
* **Proof of address** — Upload a document, choose its type from the list your
  organization's policy allows, and confirm the issuing country. Accepted types
  and recency windows come from your
  [proof-of-address policy](/core-concepts/verifications-checks#proof-of-address);
  if a retry is needed, the flow shows the reasons the previous upload was
  rejected
* **SSN** — For US-specific checks

## Step 4: Handle the result

### Via webhook (recommended)

Configure a webhook endpoint to receive real-time notifications:

```json
{
  "event": "session.approved",
  "created_at": "2026-02-01T12:05:00Z",
  "idempotency_key": "idk_a1b2c3d4e5f6...",
  "data": {
    "session_id": "session_abc123",
    "external_ref": "user_abc123",
    "status": "approved",
    "is_sandbox": false
  }
}
```

See [Webhooks](/integration-guides/webhooks-guide) for setup and signature verification.

### Via polling

Poll the session status until it reaches a terminal state:

```bash
curl https://api.withverifa.com/api/v1/sessions/session_abc123 \
  -H "X-API-Key: vk_live_your_key_here"
```

Once `status` is `completed`, fetch the result:

```bash
curl https://api.withverifa.com/api/v1/sessions/session_abc123/result \
  -H "X-API-Key: vk_live_your_key_here"
```

## Capture token expiration

The capture URL token expires after **20 minutes** by default. If the user
doesn't complete the flow in time, the session status changes to `expired`.

To re-issue a token for an expired session:

```bash
curl -X POST https://api.withverifa.com/api/v1/sessions/session_abc123/resume \
  -H "X-API-Key: vk_live_your_key_here"
```

This returns a new `capture_url` with a fresh token. Previously uploaded
documents are preserved.

## Fraud signal collection

The capture flow automatically collects 150+ fraud signals in the background:

* **Behavioral** — Completion time, hesitation patterns, copy-paste detection,
  keystroke dynamics, mouse/touch patterns
* **Device** — Browser fingerprint, bot detection, virtual camera detection,
  WebGL analysis, memory constraints
* **Network** — IP consistency, user-agent tracking, timezone validation
* **Document** — EXIF metadata analysis, blur/glare detection, screen photo
  detection, forensic analysis

These signals feed into the `risk_assessment` check during workflow execution.
You don't need to collect or submit them — the capture flow handles everything.

## Theming

Customize the capture flow appearance in the Verifa dashboard under
**Settings > Organization** (Theming section):

* Primary and accent colors
* Logo
* Font family
* Button styles
* Background colors

## Error handling

If the capture flow encounters an error, the user sees an error screen with a
retry option. Common errors:

| Error             | Cause                                | Resolution                            |
| ----------------- | ------------------------------------ | ------------------------------------- |
| Token expired     | User took too long                   | Resume the session to get a new token |
| Camera denied     | User denied camera permission        | Prompt user to allow camera access    |
| Upload failed     | Network error during document upload | Automatic retry with user prompt      |
| Session not found | Invalid or already-completed token   | Create a new session                  |

## Related

* [Sessions](/core-concepts/sessions) — Full session lifecycle and API
* [Choosing an Integration Method](/getting-started/choosing-an-integration-method) — Compare with
  other options
* [Fraud Signals](/best-practices/fraud-signals) — What signals are collected during
  capture
* [Testing](/best-practices/testing) — Test the capture flow in sandbox