Skip to main content

Idempotency

Network calls fail in ambiguous ways: a request times out, but you don't know whether the server processed it. Retrying naively risks creating a duplicate case, division, or chat message. Debitura gives you three complementary tools to retry safely:

  1. The Idempotency-Key header — an optional, opt-in key you attach to a write request so a retry replays the original outcome instead of acting twice. This is the primary mechanism and the focus of this article.
  2. Business-key idempotency — for a few operations, a natural identifier you already control (externalTenantId, creditorReference) prevents duplicates without any header.
  3. Webhook deduplication — the event id lets you process inbound deliveries exactly once.

The Idempotency-Key header

Send an Idempotency-Key request header on a supported write endpoint. The first request is processed normally and its terminal response is stored against the key. If you repeat the request with the same key and the same body, Debitura replays the stored response — its status code and body — instead of performing the operation again.

Create a case with an idempotency key
POST /cases
Content-Type: application/json
XApiKey: your-api-key
Idempotency-Key: 9f3c1b7e-2a4d-4f6a-8c11-0b2d6e5a7f10

{
"currencyCode": "EUR",
"amountToRecover": 4000.00,
"creditorReference": "INV-2024-00789",
"debtor": { ... }
}

Generate a fresh key (a UUID is ideal) per logical operation, store it client-side, and reuse the same key for every retry of that operation.

Semantics

  • Optional — omitting the header preserves the endpoint's normal behaviour.
  • Format — any string up to 255 characters. Longer values are rejected with 400.
  • Replay on match — same key + identical body returns the original stored response (status code and body), so a retry never creates a second resource.
  • Conflict on mismatch — reusing a key with a different body returns 422 Unprocessable Entity with Type: "IdempotencyConflict". A key is bound to the exact request it was first used with.
  • In-flight contention — if a request with the same key is still being processed, the retry returns 409 Conflict with Type: "IdempotencyKeyInProgress" and a Retry-After: 2 header. Wait and retry.
  • Only terminal outcomes are stored — a successful create (2xx) or a business-rule rejection (422) is stored and will replay.
  • Server errors (5xx) depend on how far the request got — if the operation failed before the resource was created, nothing is stored and a retry genuinely re-attempts the work. If the resource was already created and a later step failed, the 5xx is stored and replayed on retry. This is deliberate: once the resource exists, a retry must never create a duplicate.
  • Field validation is never stored — field-level 400 validation errors are not persisted. You can fix the payload and retry with the same key.
  • Keys expire after 24 hours — a stored key is retained for 24 hours from the first request. Reusing the same key (even with an identical body) after it expires starts a fresh operation and creates a new resource, so always retry within the 24-hour window.

Response scenarios

ScenarioResponse
First request, succeedsNormal success (e.g. 200/201) — response is stored
Retry, same key + same bodyOriginal stored response (status code and body), replayed
Retry, same key + different body422 · Type: "IdempotencyConflict"
Concurrent retry while first is in flight409 · Type: "IdempotencyKeyInProgress" · Retry-After: 2
Key longer than 255 characters400 validation error
First request fails field validation (400)Not stored — fix and retry with the same key
Request fails with 5xx before the resource is createdNot stored — retry re-attempts the operation
Request fails with 5xx after the resource is createdStored — retry replays the 5xx, never re-creates
Retry, same key + same body, more than 24h laterKey has expired — treated as a new request, creates a new resource

Supported endpoints

APIEndpointOperation
Customer APIPOST /casesCreate a collection case
Customer APIPOST /divisionsCreate a division
Collection Partner APIPOST /managed-casesCreate a case as managing partner
Collection Partner APIPOST /ingestion-jobsEnqueue a batch of ingest events
Collection Partner APIPOST /cases/{id}/chatsSend a chat message (see variant below)
Referral Partner APIPOST /clients/{externalTenantId}/kyc-verificationSubmit KYC verification
Referral Partner APIPOST /cases/{caseId}/filesUpload a file to a case

Each supported operation lists the Idempotency-Key header in its API reference. Endpoints not in this table ignore the header.

:::note Webhooks excluded by design POST /webhooks (subscription creation) does not support Idempotency-Key. The signing secret it returns is shown once and cannot be replayed, and the subscription URL is already unique per endpoint, so duplicate subscriptions are prevented without a key. :::

Chat message-sending variant

POST /cases/{id}/chats on the Collection Partner API honours Idempotency-Key but uses a lighter, in-memory mechanism rather than the durable store described above:

  • A duplicate key with the same message body within 24 hours returns the originally created chat message instead of posting a second one.
  • The key is scoped per partner + case + message body. Reusing the same key with a different message body posts a new message — it does not return a 422 conflict.
  • The key is treated as an opaque cache entry. Unlike the durable endpoints, its length is not validated here, so the 255-character limit shown in the API reference is not enforced for this endpoint.
  • Because the cache is in-memory and expires after 24 hours, treat this as best-effort de-duplication for rapid retries, not a durable guarantee.

Business-key idempotency

Some operations are naturally idempotent through an identifier you already supply — no header required.

Client creation (Referral Partner API)

POST /clients is idempotent on your externalTenantId:

  • The first call creates the client; repeat calls with the same externalTenantId return the existing client's current status rather than creating a duplicate.
  • The response code tracks onboarding progress (201 fully onboarded, 202 onboarding incomplete, 409 when the client exists but needs linking or is linked to another partner).

This lets you retry client creation safely without tracking request state yourself. See Client onboarding conflicts for the conflict cases.

Case reference uniqueness (Customer API)

Independently of the Idempotency-Key header, creditorReference must be unique per creditor. A duplicate reference is rejected with 422, which prevents accidental duplicate cases even when no key is supplied. For the strongest guarantee, combine a unique creditorReference with an Idempotency-Key.

Webhook consumption

Idempotency also applies to events you receive. Debitura retries failed webhook deliveries with exponential backoff, so your endpoint may see the same event more than once. Deduplicate on the event id:

Webhook event payload
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"event": "case.created",
"timestamp": "2026-01-15T10:30:00Z",
"data": { ... }
}
  1. Extract id from the payload.
  2. If you've already processed that id, return 200 OK without re-acting.
  3. Otherwise process the event and record the id.

See Webhooks for delivery and retry mechanics.

Safe retry checklist

OperationHow to retry safely
Any supported write endpointAttach a stable Idempotency-Key; reuse the same key on every retry within 24h
Create client (Referral Partner API)Retry directly — idempotent on externalTenantId
Create case (Customer API)Send a unique creditorReference; optionally add an Idempotency-Key
Send chat messageReuse the same Idempotency-Key within 24h for identical retries
Process inbound webhookDeduplicate locally on the event id

When in doubt, generate one Idempotency-Key per operation up front, persist it alongside the work you're trying to do, and send it on the first attempt and every retry within 24 hours. That single habit makes the ambiguous-timeout case safe across every supported endpoint.