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

# Person Verification

> Verify an individual end-to-end with document capture, biometric liveness, face matching, database checks, and screening.

# Person Verification

Verify an individual end-to-end with document capture, biometric liveness, face matching, database checks, and screening. This guide covers initiating a journey, redirecting the user, and interpreting structured results.

## What Person Verification checks

A Person Verification journey orchestrates up to six check types:

1. **Identity Document Capture**. Passport, national ID, or driving licence scanned with OCR.
2. **Biometric Liveness**. Selfie with liveness detection to confirm the user is physically present.
3. **Face Match**. Compares the selfie against the document photo.
4. **Document Authenticity**. Tamper detection and fraud analysis on the captured document.
5. **Database Verification**. Name, address, and date of birth validated against official databases.
6. **PEP/Sanctions Screening** (WorldCheck). Screening on the individual across PEP, sanctions, adverse media, and watchlist databases.

Checks 1 through 4 require user interaction and use the async journey flow. Checks 5 and 6 can run server-side without user interaction. See [Server-side Person Verification](#server-side-person-verification-no-user-interaction) below.

## Data tiers

### Tier 1: Minimum

The bare minimum to initiate a Person Verification journey.

```json theme={null}
{
  "first_name": "Jane",
  "last_name": "Smith",
  "country": "GB"
}
```

### Tier 2: Standard

Adds date of birth, contact details, and address. This is the recommended minimum for production.

```json theme={null}
{
  "first_name": "Jane",
  "last_name": "Smith",
  "country": "GB",
  "date_of_birth": "1990-06-15",
  "email": "jane.smith@example.com",
  "phone": "+447700900123",
  "address": "42 Baker Street",
  "city": "London",
  "postal_code": "NW1 6XE"
}
```

### Tier 3: Full

Adds nationality, document type preference, and external reference for idempotency.

```json theme={null}
{
  "first_name": "Jane",
  "last_name": "Smith",
  "country": "GB",
  "date_of_birth": "1990-06-15",
  "email": "jane.smith@example.com",
  "phone": "+447700900123",
  "address": "42 Baker Street",
  "city": "London",
  "postal_code": "NW1 6XE",
  "nationality": "GB",
  "document_type": "passport",
  "external_reference": "YOUR-REF-2026-0123"
}
```

## Flow overview

```
Your App                    Zenoo                     User
   |                          |                         |
   |---- 1. POST /kyc/init -->|                         |
   |<--- Tokens (pull+start) -|                         |
   |                          |                         |
   |---- 2. Redirect user -------------------------------->|
   |                          |<-- 3. Doc + selfie ------|
   |                          |--- 4. Process checks --->|
   |                          |                         |
   |<--- 5. Webhook ----------|                         |
   |---- 6. GET /results ---->|                         |
   |<--- Full results --------|                         |
```

<Steps>
  <Step title="Initiate the journey">
    Person Verification uses async Model 2 because the user must interact with the verification UI. Post to the `/kyc/init` endpoint.

    ```bash theme={null}
    curl -X POST \
      "https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/kyc/init" \
      -H "Content-Type: application/json" \
      -H "X-API-KEY: your-api-key" \
      -d '{
        "first_name": "Jane",
        "last_name": "Smith",
        "date_of_birth": "1990-06-15",
        "email": "jane.smith@example.com",
        "phone": "+447700900123",
        "country": "GB",
        "address": "42 Baker Street",
        "city": "London",
        "postal_code": "NW1 6XE",
        "external_reference": "YOUR-REF-2026-0123"
      }'
    ```

    **Response:**

    ```json theme={null}
    {
      "tokens": {
        "pull": "eyJhbGciOiJIUzI1NiJ9.pull-token...",
        "start": "eyJhbGciOiJIUzI1NiJ9.start-token..."
      }
    }
    ```

    Store both tokens immediately:

    * **`pull`**. Retrieves verification results.
    * **`start`**. Constructs the verification URL for the user.
  </Step>

  <Step title="Construct the verification URL and redirect the user">
    Build the URL from the `start` token and your project hash:

    ```
    https://instance.prod.onboardapp.io/{project_hash}/?t={start_token}
    ```

    ### Web integration

    ```html theme={null}
    <a
      href="https://instance.prod.onboardapp.io/{project_hash}/?t={start_token}"
      target="_blank"
    >
      Verify Your Identity
    </a>
    ```

    Or redirect server-side:

    ```javascript server.js theme={null}
    res.redirect(
      302,
      `https://instance.prod.onboardapp.io/${projectHash}/?t=${startToken}`
    );
    ```

    ### Mobile integration

    Open the URL in a WebView or the system browser. The verification UI is responsive and works on all screen sizes.

    <CodeGroup>
      ```swift iOS theme={null}
      let url = URL(string: "https://instance.prod.onboardapp.io/\(projectHash)/?t=\(startToken)")!
      UIApplication.shared.open(url)
      ```

      ```kotlin Android theme={null}
      val intent = Intent(Intent.ACTION_VIEW,
          Uri.parse("https://instance.prod.onboardapp.io/$projectHash/?t=$startToken"))
      startActivity(intent)
      ```
    </CodeGroup>

    ### URL expiry

    <Warning>
      Verification URLs expire after **24 hours**. If the user does not complete the flow within this window, you receive a `journey.expired` webhook and must initiate a new journey.
    </Warning>
  </Step>

  <Step title="User completes verification">
    The user goes through an interactive flow:

    1. **Document selection**. Choose a document type: passport, national ID, or driving licence.
    2. **Document capture**. Photograph the front (and back if applicable) of the document.
    3. **Selfie capture**. Take a selfie for biometric matching.
    4. **Liveness check**. Follow on-screen prompts to prove physical presence.
    5. **Submission**. Data is sent to Zenoo for processing.

    Server-side processing typically completes 10 to 30 seconds after the user submits.
  </Step>

  <Step title="Receive results">
    ### Option A: Webhook (recommended)

    Configure a webhook endpoint to receive `verification.completed` events.

    ```json theme={null}
    {
      "event_type": "verification.completed",
      "timestamp": "2026-01-15T14:35:00Z",
      "journey_id": "jrn-abc123",
      "callback_reference": "YOUR-REF-2026-0123",
      "status": "completed",
      "data": { "...": "..." }
    }
    ```

    See [Webhooks Guide](/guides/webhooks) for setup.

    ### Option B: Poll for results

    ```bash theme={null}
    curl -X GET \
      "https://instance.prod.onboardapp.io/api/gateway/sharable-payload/{pull_token}"
    ```

    | Status               | Meaning                               |
    | -------------------- | ------------------------------------- |
    | `200` with JSON body | Results are ready                     |
    | `204` No Content     | Still processing, retry after a delay |
    | `404`                | Invalid or expired token              |

    ### Option C: Both

    Use webhooks for real-time notification and polling as a fallback for missed webhooks. This is the most resilient pattern.
  </Step>

  <Step title="Interpret the results">
    A complete Person Verification response includes identity verification, document details, biometric results, screening, phone verification, email verification, address verification, and compliance metadata.

    ```json {8,12} theme={null}
    {
      "case_reference": "AML-2026-0123",
      "external_reference": "YOUR-REF-2026-0123",
      "processing_status": "complete",
      "completed_at": "2026-01-15T14:35:30Z",

      "identity": {
        "verified": true,
        "decision": "Pass",
        "score": 95,
        "authentication_id": "auth-xyz789"
      },

      "document": {
        "verified": true,
        "document_type": "Passport",
        "document_country": "GB",
        "document_number": "123456789",
        "validation_result": "PASSED",
        "tamper_result": "Pass",
        "extracted_data": {
          "full_name": "JANE ELIZABETH SMITH",
          "date_of_birth": "1990-06-15",
          "expiration_date": "2030-12-01",
          "gender": "F",
          "nationality": "GBR",
          "mrz_line_1": "P<GBRSMITH<<JANE<ELIZABETH<<<<<<<<<<<<<<<<<<",
          "mrz_line_2": "1234567890GBR9006150F3012013<<<<<<<<<<<<<<00"
        }
      },

      "biometric": {
        "liveness_verified": true,
        "liveness_assessment": "Live",
        "liveness_score": 98.5,
        "face_match": true,
        "face_match_score": 96.2
      },

      "screening": {
        "pep_status": "No Hit",
        "sanctions_status": "No Hit",
        "adverse_media_status": "No Hit",
        "watchlist_status": "No Hit",
        "screening_provider": "WorldCheck",
        "screening_completed_at": "2026-01-15T14:35:10Z",
        "total_matches": 0
      },

      "phone_verification": {
        "verified": true,
        "phone_number": "+447700900123",
        "carrier": "Vodafone UK",
        "line_type": "mobile",
        "verified_at": "2026-01-15T14:35:12Z"
      },

      "email_verification": {
        "verified": true,
        "email": "jane.smith@example.com",
        "domain_valid": true,
        "disposable": false,
        "verified_at": "2026-01-15T14:35:12Z"
      },

      "address_verification": {
        "verified": true,
        "match_level": "Full",
        "normalized_address": "42 Baker Street, London, NW1 6XE, GB",
        "verified_at": "2026-01-15T14:35:14Z"
      },

      "compliance_metadata": {
        "cdd_completed": true,
        "cdd_completed_at": "2026-01-15T14:35:30Z",
        "edd_required": false,
        "reviewed_by": "system",
        "risk_tier": "Low"
      }
    }
    ```

    ### Decision matrix

    | Identity | Document  | Biometric | Screening     | Action                                  |
    | -------- | --------- | --------- | ------------- | --------------------------------------- |
    | Pass     | Pass      | Pass      | No Hit        | Auto-approve                            |
    | Pass     | Pass      | Pass      | Hit           | Manual screening review required        |
    | Pass     | Attention | Pass      | No Hit        | Manual document review                  |
    | Pass     | Pass      | Fail      | No Hit        | Request new biometric capture           |
    | Fail     | Any       | Any       | Any           | Reject or request re-submission         |
    | Any      | Fail      | Any       | Any           | Reject document, request new submission |
    | Any      | Any       | Any       | Sanctions Hit | Immediate halt. Escalate to compliance. |

    <Tip>
      Only auto-approve the `Pass / Pass / Pass / No Hit` combination. Any other outcome should trigger manual review or re-submission.
    </Tip>

    <Note>
      A document `ATTENTION` result means the document requires human review, not necessarily rejection. Check `document.validation_result` before making a pass/fail decision.
    </Note>

    ### Check dimension statuses

    **Identity statuses:**

    | Status | Meaning                                                   |
    | ------ | --------------------------------------------------------- |
    | Pass   | Name, address, and DOB confirmed against database records |
    | Fail   | Could not verify identity against databases               |

    **Document statuses:**

    | Status    | Meaning                                                                            |
    | --------- | ---------------------------------------------------------------------------------- |
    | Pass      | Document is authentic, not expired, and data matches                               |
    | Attention | Possible issue detected (blurry image, minor inconsistency). Manual review needed. |
    | Fail      | Document rejected (expired, tampered, unrecognized)                                |

    **Biometric statuses:**

    | Status | Meaning                                            |
    | ------ | -------------------------------------------------- |
    | Pass   | Liveness confirmed and face matches document photo |
    | Fail   | Liveness check failed or face does not match       |

    **Screening statuses:**

    | Status | Meaning                                                             |
    | ------ | ------------------------------------------------------------------- |
    | No Hit | No matches in PEP, sanctions, adverse media, or watchlist databases |
    | Hit    | One or more potential matches found. Review required.               |
  </Step>
</Steps>

## Handling edge cases

### journey.abandoned

The user closed the browser or navigated away before completing verification.

```json theme={null}
{
  "event_type": "journey.abandoned",
  "journey_id": "jrn-abc123",
  "callback_reference": "YOUR-REF-2026-0123"
}
```

The original verification URL remains valid until it expires. Send the user a reminder with the same link. If the URL has expired, initiate a new journey.

### journey.expired

The 24-hour URL expired before the user completed the flow.

```json theme={null}
{
  "event_type": "journey.expired",
  "journey_id": "jrn-abc123",
  "callback_reference": "YOUR-REF-2026-0123"
}
```

Initiate a new journey and send the user a fresh link.

### Document issues

If the document fails authenticity checks, `document.validation_result` will be `ATTENTION` or `FAILED`. Common causes:

* Blurry or glare-obscured image
* Expired document
* Tamper indicators detected
* Document type not supported for the selected country

Check `document.validation_result` before making a pass/fail decision. An `ATTENTION` result means the document requires human review, not necessarily rejection.

## Server-side Person Verification (no user interaction)

For database lookups, phone verification, and screening that do not require document capture, use the sync endpoint directly.

```bash theme={null}
curl -X POST \
  "https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/kyc/api" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: your-api-key" \
  -H "X-SYNC-TIMEOUT: 30000" \
  -d '{
    "first_name": "Jane",
    "last_name": "Smith",
    "date_of_birth": "1990-06-15",
    "phone": "+447700900123",
    "email": "jane.smith@example.com",
    "country": "GB",
    "address": "42 Baker Street",
    "city": "London",
    "postal_code": "NW1 6XE"
  }'
```

This runs database verification, phone checks, email checks, address checks, and screening synchronously. Results are returned in the response body. No document capture or biometric checks are performed.

Use server-side Person Verification for:

* Batch processing existing customer data
* Pre-screening before initiating a full journey
* Ongoing monitoring re-checks on known individuals
* Flows where you already have verified identity documents from another source

## Next steps

<Columns>
  <Card title="Screening" icon="magnifying-glass" href="/use-cases/screening">
    Run standalone screening checks
  </Card>

  <Card title="Webhooks Guide" icon="bell" href="/guides/webhooks">
    Set up real-time notifications
  </Card>

  <Card title="Sync vs Async" icon="arrows-rotate" href="/guides/sync-vs-async">
    Understand when to use each execution model
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/guides/error-handling">
    Handle failures and retries
  </Card>
</Columns>
