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

# Sync vs Async

> Choose between synchronous and asynchronous execution models based on your integration requirements.

# Sync vs Async Flows

Zenoo supports two execution models. Choose based on whether user interaction is required and how your system handles latency.

<Tip>
  Quick rule: server-to-server checks use sync. User-facing journeys use async.
</Tip>

<Tabs>
  <Tab title="Synchronous">
    ## Synchronous (Model 1)

    The API blocks until all checks complete or the timeout is reached. Results are returned in the response body.

    ```mermaid theme={null}
    sequenceDiagram
        participant Y as Your Server
        participant Z as Zenoo
        participant P as Providers
        Y->>Z: POST /api (X-SYNC-TIMEOUT)
        Z->>P: Execute checks
        P-->>Z: Results
        Z-->>Y: 200 OK + full results
    ```

    ### How to use

    Add the `X-SYNC-TIMEOUT` header to any `/api` endpoint. The value is in milliseconds.

    ```bash theme={null}
    curl -X POST \
      "https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
      -H "Content-Type: application/json" \
      -H "X-API-KEY: your-api-key" \
      -H "X-SYNC-TIMEOUT: 60000" \
      -d '{
        "company_name": "Acme Holdings Ltd",
        "registration_number": "12345678",
        "country": "GB"
      }'
    ```

    The API holds the connection open for up to `X-SYNC-TIMEOUT` milliseconds, then returns whatever results are available.

    ### When to use

    * Server-to-server checks: AML screening, company registry verification
    * Automated pipelines: batch processing, background jobs, cron tasks
    * Simple flows: single-provider checks that complete quickly
    * Development and testing: fastest way to see results

    ### Timeout behavior

    <Note>
      If the response contains a `tokens` field, some checks are still processing. Use the `pull` token to retrieve the remaining results.
    </Note>

    | Scenario                             | Response                                           |
    | ------------------------------------ | -------------------------------------------------- |
    | All checks complete within timeout   | `200` with full results                            |
    | Timeout reached, some checks pending | Partial results with `tokens` for polling the rest |
    | Provider error                       | Error response with details and retry guidance     |

    ### Recommended timeouts

    | Check type                            | Timeout   | Notes                                               |
    | ------------------------------------- | --------- | --------------------------------------------------- |
    | AML screening only                    | 15,000 ms | Single provider call, usually completes in 3-10s    |
    | Company registry lookup               | 30,000 ms | Depends on jurisdiction, some registries are slower |
    | Full Company (registry + screening)   | 60,000 ms | Multiple providers in sequence                      |
    | Server-side Person (database + phone) | 30,000 ms | Multiple lightweight checks in parallel             |

    <Warning>
      Setting timeouts below 10 seconds forces unnecessary polling. Setting them above 90 seconds ties up HTTP connections without benefit. Start with the recommended values and adjust based on your p95 latency.
    </Warning>
  </Tab>

  <Tab title="Asynchronous">
    ## Asynchronous (Model 2)

    The API returns immediately with tokens. Processing happens in the background.

    ```mermaid theme={null}
    sequenceDiagram
        participant Y as Your Server
        participant Z as Zenoo
        participant P as Providers
        Y->>Z: POST /init
        Z-->>Y: 200 OK + tokens (pull, start)
        Z->>P: Execute checks (background)
        P-->>Z: Results
        Y->>Z: GET /sharable-payload/{pull}
        Z-->>Y: 204 (still processing)
        Y->>Z: GET /sharable-payload/{pull}
        Z-->>Y: 200 OK + full results
    ```

    ### How to use

    Call the `/init` endpoint without the `X-SYNC-TIMEOUT` header.

    ```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",
        "country": "GB",
        "external_reference": "YOUR-REF-2026-0123"
      }'
    ```

    **Response:**

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

    ### When to use

    * User-facing journeys: Person Verification document capture, biometric liveness
    * Long-running flows: multi-provider checks, manual review steps
    * User interaction required: the user must upload a document or complete liveness
    * High-volume processing: avoid holding HTTP connections open

    ### Token pair

    The response contains two tokens:

    | Token   | Purpose                                                         | Lifetime             |
    | ------- | --------------------------------------------------------------- | -------------------- |
    | `pull`  | Retrieve results via `GET /api/gateway/sharable-payload/{pull}` | Until results expire |
    | `start` | Construct the verification URL for user-facing flows            | 24 hours             |

    Store both tokens in your database immediately. The `pull` token is the only way to retrieve results. If you lose it, you must reinitiate the verification.

    ### Verification URL

    For Person Verification flows, construct the verification URL from the `start` token:

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

    Redirect the user to this URL to begin document capture and biometric verification. The URL expires after 24 hours.

    ### Result retrieval

    **Polling.** Call the pull endpoint and check the HTTP status code.

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

    **Webhook.** Configure an endpoint to receive `verification.completed` events. See [Webhooks Guide](/guides/webhooks).

    **Both (recommended).** Use webhooks for real-time notification, with polling as a fallback for missed webhooks.
  </Tab>
</Tabs>

## Comparison table

| Aspect               | Sync (Model 1)                         | Async (Model 2)                          |
| -------------------- | -------------------------------------- | ---------------------------------------- |
| **Endpoint suffix**  | `/api`                                 | `/init`                                  |
| **Response type**    | Full results (or partial + tokens)     | Tokens only                              |
| **Blocking**         | Yes, up to timeout                     | No                                       |
| **User interaction** | Not supported                          | Supported via start token                |
| **Result retrieval** | In response body                       | Pull endpoint or webhook                 |
| **Best for**         | Screening, Company, server-side Person | Person with document capture, biometrics |
| **Timeout control**  | `X-SYNC-TIMEOUT` header (ms)           | N/A                                      |
| **Typical latency**  | 10-60 seconds                          | Instant (tokens), results in 30s-5min    |

## Processing timeline

Typical durations for each processing stage:

| Stage                                         | Duration          |
| --------------------------------------------- | ----------------- |
| API gateway routing                           | \< 1 second       |
| Company registry lookup                       | 5-15 seconds      |
| PEP/sanctions screening (WorldCheck)          | 3-10 seconds      |
| Document verification (after user submits)    | 10-30 seconds     |
| Biometric liveness (after user submits)       | 5-15 seconds      |
| Risk model evaluation                         | 1-5 seconds       |
| **Full Company Verification (automated)**     | **15-45 seconds** |
| **Full Person Verification (user-dependent)** | **1-30 minutes**  |

Person Verification elapsed time is dominated by user behavior: finding documents, taking photos, retrying failed captures. Once the user submits, server-side processing completes in 30 to 60 seconds.

## Mixing models

Most production integrations use both models. A typical pattern:

1. **Company Verification** (sync). Submit company data, get results in the response.
2. **Person Verification** (async). Initiate journeys for directors and UBOs, redirect them to verification URLs.
3. **Screening** (sync). Re-screen existing customers for ongoing monitoring.

```javascript integration.js theme={null}
// 1. Company Verification - sync
const companyResult = await fetch(
  `${BASE_URL}/api/gateway/execute/${PROJECT_HASH}/api`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": API_KEY,
      "X-SYNC-TIMEOUT": "60000"
    },
    body: JSON.stringify({
      company_name: "Acme Holdings Ltd",
      registration_number: "12345678",
      country: "GB"
    })
  }
);
const companyData = await companyResult.json();

// 2. Person Verification - async (for each director/UBO)
const personInit = await fetch(
  `${BASE_URL}/api/gateway/execute/${PROJECT_HASH}/kyc/init`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": API_KEY
    },
    body: JSON.stringify({
      first_name: "Jane",
      last_name: "Smith",
      country: "GB",
      external_reference: "YOUR-REF-2026-0123"
    })
  }
);
const { tokens } = await personInit.json();
const verificationUrl = `${BASE_URL}/${PROJECT_HASH}/?t=${tokens.start}`;
// Send verificationUrl to the user via email or in-app

// 3. Screening - sync (ongoing monitoring)
const screenResult = await fetch(
  `${BASE_URL}/api/gateway/execute/${PROJECT_HASH}/screening/api`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": API_KEY,
      "X-SYNC-TIMEOUT": "15000"
    },
    body: JSON.stringify({
      name: "John Smith",
      date_of_birth: "1975-03-20",
      country: "GB",
      entity_type: "person",
      categories: ["pep", "sanctions", "adverse_media", "watchlist"]
    })
  }
);
```

## Polling example (Node.js)

A complete polling implementation with exponential backoff:

```javascript poll-results.js theme={null}
async function pollForResults(pullToken, options = {}) {
  const {
    baseUrl = "https://instance.prod.onboardapp.io",
    maxAttempts = 30,
    initialDelay = 10_000, // 10 seconds
    maxDelay = 30_000, // 30 seconds
    backoffAfter = 12 // switch to maxDelay after 12 attempts (2 min)
  } = options;

  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const response = await fetch(
      `${baseUrl}/api/gateway/sharable-payload/${pullToken}`
    );

    if (response.status === 200) {
      return await response.json();
    }

    if (response.status === 404) {
      throw new Error("Invalid or expired pull token");
    }

    if (response.status !== 204) {
      throw new Error(`Unexpected status: ${response.status}`);
    }

    // 204 - still processing
    const delay = attempt < backoffAfter ? initialDelay : maxDelay;
    await new Promise((resolve) => setTimeout(resolve, delay));
  }

  throw new Error(`Polling timed out after ${maxAttempts} attempts`);
}

// Usage
try {
  const results = await pollForResults("eyJhbGciOiJIUzI1NiJ9.pull-token...");
  console.log("Verification complete:", results.processing_status);
} catch (err) {
  console.error("Polling failed:", err.message);
}
```

## Next steps

* [Webhooks Guide](/guides/webhooks). Set up async result delivery.
* [Error Handling](/guides/error-handling). Handle timeouts, retries, and provider failures.
* [Testing and Sandbox](/testing/staging-environment). Test both models in the staging environment.
