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

# Idempotency

> Prevent duplicate verifications using the external_reference field for idempotent requests.

# Idempotency

Zenoo supports idempotent requests through the `external_reference` field. This prevents duplicate verifications caused by retries, user double-clicks, network failures, or asynchronous workflow replays.

## How It Works

Include an `external_reference` in any verification request. If you submit the same `external_reference` twice:

1. The second request returns the existing case's tokens.
2. No duplicate verification is created.
3. No additional charges apply.

The `external_reference` is scoped to your project. Two different projects can use the same reference value without conflict.

## Company Verification Example

```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: 30000" \
  -d '{
    "company_name": "Acme Holdings Ltd",
    "registration_number": "12345678",
    "country": "GB",
    "external_reference": "ONBOARD-2026-0042"
  }'
```

## Person Verification Example

```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": "PERSON-2026-0789"
  }'
```

## Duplicate Detection Behavior

When Zenoo receives a request with an `external_reference` that already exists in your project:

```
First request:   external_reference: "ONBOARD-2026-0042"
                  -> Creates new case, returns tokens A

Second request:  external_reference: "ONBOARD-2026-0042"
                  -> Returns tokens A (same case, no new verification)
```

The response to the second request is identical to the first. You get back the same `pull` and `start` tokens pointing to the original case.

### What Happens to the Request Body

<Note>
  The second request's body fields (company name, country, etc.) are ignored. Zenoo returns the existing case based solely on the `external_reference` match. If you need to run a new verification with different data for the same entity, use a different `external_reference`.
</Note>

## When to Use

<Info>
  Always, in production. The `external_reference` field protects against accidental duplicates with no downside.
</Info>

The `external_reference` field protects against:

* **Network retries.** If your HTTP client retries a timed-out request, you do not get charged twice.
* **User double-clicks.** If a user clicks "Verify" twice, only one verification runs.
* **Workflow replays.** If your orchestration system replays a failed step, the verification is not duplicated.
* **Distributed systems.** If two instances of your service submit the same verification concurrently, one case is created.

## Best Practices

**Use your internal identifiers.** Map `external_reference` to something unique in your system:

| Your System            | Example Reference           |
| ---------------------- | --------------------------- |
| Loan application       | `APP-2026-0042`             |
| Customer onboarding    | `ONBOARD-2026-1234`         |
| Periodic review        | `REVIEW-2026-Q1-0042`       |
| Person re-verification | `PERSON-REVERIFY-2026-0042` |

**Include it in every production request.** There is no downside and it prevents accidental duplicates.

<Tip>
  Make references deterministic. Derive them from your data (application ID, customer ID + date) rather than generating random UUIDs. This ensures the same logical operation always produces the same reference.
</Tip>

```javascript reference-generator.js theme={null}
// Good: deterministic, derived from your data
const reference = `APP-${applicationId}`;

// Also good: includes a scope
const reference = `REVIEW-${customerId}-${quarter}`;

// Bad: random UUID defeats the purpose
const reference = crypto.randomUUID();
```

**Include the scope in the reference.** If the same entity can have multiple verification types (initial onboarding, periodic review, re-verification), include the type in the reference to avoid collisions:

```
ONBOARD-CUST-1234       // initial onboarding
REVIEW-CUST-1234-2026Q1 // quarterly review
REVERIFY-CUST-1234-EDD  // EDD re-verification
```

## Webhook Deduplication

Idempotency also matters on the receiving side. Zenoo may deliver the same webhook event more than once.

Use `journey_id` combined with `event_type` as a deduplication key:

```javascript server.js theme={null}
app.post("/webhooks/zenoo", async (req, res) => {
  const { journey_id, event_type } = req.body;
  const dedupeKey = `${journey_id}:${event_type}`;

  const existing = await db.webhookEvents.findOne({ dedupeKey });
  if (existing) {
    return res.status(200).json({ received: true, duplicate: true });
  }

  await db.webhookEvents.create({ dedupeKey, payload: req.body });
  await queue.enqueue("process-webhook", req.body);

  res.status(200).json({ received: true });
});
```

<Warning>
  Always return `200` for duplicates. Returning an error triggers unnecessary retries.
</Warning>

## Testing Idempotency

Verify that duplicate requests return the same case:

```bash theme={null}
# First request
curl -X POST "https://instance.staging.onboardapp.io/api/gateway/execute/{hash}/api" \
  -H "X-API-KEY: staging-key" \
  -H "X-SYNC-TIMEOUT: 30000" \
  -d '{
    "company_name": "Test Idempotent Ltd",
    "registration_number": "TEST-IDEM-001",
    "country": "GB",
    "external_reference": "idem-test-001"
  }'
# Save the case_reference and tokens from the response

# Second request (identical external_reference)
curl -X POST "https://instance.staging.onboardapp.io/api/gateway/execute/{hash}/api" \
  -H "X-API-KEY: staging-key" \
  -H "X-SYNC-TIMEOUT: 30000" \
  -d '{
    "company_name": "Test Idempotent Ltd",
    "registration_number": "TEST-IDEM-001",
    "country": "GB",
    "external_reference": "idem-test-001"
  }'
# Verify: same case_reference returned, same tokens, no duplicate case
```

You should receive the same `case_reference` and tokens in both responses. No new verification is created.

## Next Steps

* [Error Handling](/guides/error-handling) -- Retry strategies and backoff patterns
* [Webhooks](/guides/webhooks) -- Webhook deduplication in detail
* [Result Delivery](/guides/result-delivery) -- Retrieving verification results
