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 stable event id lets you make inbound processing idempotent.

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 — a non-empty, non-whitespace string up to 255 characters. Invalid 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 businessErrors[0].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 a Retry-After: 2 header. Wait and retry. The error envelope depends on the endpoint: POST /clients returns businessErrors[0].type: "IdempotencyKeyInProgress". Treat any 409 with Retry-After as in-flight contention rather than assuming a top-level Type. The chat-send endpoint is the exception — it does not perform in-flight reservation, so concurrent retries there rely on the stored record instead of a 409.
  • Only terminal outcomes are stored — a successful create (2xx) or a business-rule rejection (422) is stored and will replay. On Referral Partner API POST /clients, a terminal 409 onboarding or linking response is stored too; in-flight 409 responses with Retry-After are not.
  • 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 a 24-hour replay window — ordinary completed operations are replayable for 24 hours. Long-running operations may refresh their active reservation while work is in flight, so the window is not guaranteed to end exactly 24 hours after initial receipt. Reusing the same key after its stored window expires starts a fresh operation and may create a new resource, so keep normal retries well within 24 hours.

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 · businessErrors[0].type: "IdempotencyConflict"
Concurrent retry while first is in flight409 · Retry-After: 2; on POST /clients, businessErrors[0].type: "IdempotencyKeyInProgress"
Key longer than 255 characters400 validation error
Key empty or whitespace-only400 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 after the stored key's 24h replay windowKey has expired — treated as a new request and may create 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}/paymentsRecord a payment on a case
Collection Partner APIPOST /cases/{id}/chatsSend a chat message (see variant below)
Collection Partner APIPOST /cases/{id}/filesUpload a file to a case
Collection Partner APIPOST /managed-cases/{id}/filesUpload a file to a managed case
Collection Partner APIPOST /managed-clientsCreate a represented client (header required here: exactly one value of 1–255 non-whitespace characters; missing or invalid → 400 InvalidIdempotencyKey)
Referral Partner APIPOST /clientsCreate or link a client and optionally create cases
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.

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)​

Without Idempotency-Key, POST /clients uses externalTenantId only as a client/link business key:

  • 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 business key prevents duplicate clients and links, but it does not make an unkeyed case-bearing request safe to retry and it does not replay the original response. Case-safe retry and exact response replay require Idempotency-Key: it binds the complete effective request body, replays the exact original response, and prevents duplicate case creation when cases is present. Use one key per client-creation attempt and reuse it for unchanged retries within 24 hours. See Client onboarding conflicts for the conflict cases.

“Effective” follows what POST /clients actually executes. A single user is always inferred as the onboarding user; omitted and empty cases or claimLines collections are equivalent. Within a case, changing skipCreationEmails, allowPendingContracts, or assignedUserEmail does not create a new effective request on this endpoint. Unsafe returnUrl values are dropped, debtor type casing is normalized, contactPerson is ignored for a private debtor, and state is ignored when stateAlpha2 is supplied. Retrying with the same key across any of those syntax-only differences replays the first terminal response; changing a field that affects creation still returns IdempotencyConflict.

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. Atomically claim that id in the same transaction as your business-side effect.
  3. If the claim already exists, return 200 OK without re-acting; otherwise commit the effect and claim together.

If your storage cannot make the claim and business effect atomic, make the effect itself idempotent and record the event ID before acknowledging the delivery.

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)Send an Idempotency-Key when the request includes cases or exact response replay matters; externalTenantId still prevents duplicate clients when the header is omitted
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.