Skip to main content

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

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

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

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.

When to Use

Always, in production. The external_reference field protects against accidental duplicates with no downside.
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 SystemExample Reference
Loan applicationAPP-2026-0042
Customer onboardingONBOARD-2026-1234
Periodic reviewREVIEW-2026-Q1-0042
Person re-verificationPERSON-REVERIFY-2026-0042
Include it in every production request. There is no downside and it prevents accidental duplicates.
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.
reference-generator.js
// 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:
server.js
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 });
});
Always return 200 for duplicates. Returning an error triggers unnecessary retries.

Testing Idempotency

Verify that duplicate requests return the same case:
# 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