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:
- The
Idempotency-Keyheader — 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. - Business-key idempotency — for a few operations, a natural identifier you already control (
externalTenantId,creditorReference) prevents duplicates without any header. - Webhook deduplication — the event
idlets 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.
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 EntitywithType: "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 ConflictwithType: "IdempotencyKeyInProgress"and aRetry-After: 2header. 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, the5xxis 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
400validation 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
| Scenario | Response |
|---|---|
| First request, succeeds | Normal success (e.g. 200/201) — response is stored |
| Retry, same key + same body | Original stored response (status code and body), replayed |
| Retry, same key + different body | 422 · Type: "IdempotencyConflict" |
| Concurrent retry while first is in flight | 409 · Type: "IdempotencyKeyInProgress" · Retry-After: 2 |
| Key longer than 255 characters | 400 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 created | Not stored — retry re-attempts the operation |
Request fails with 5xx after the resource is created | Stored — retry replays the 5xx, never re-creates |
| Retry, same key + same body, more than 24h later | Key has expired — treated as a new request, creates a new resource |
Supported endpoints
| API | Endpoint | Operation |
|---|---|---|
| Customer API | POST /cases | Create a collection case |
| Customer API | POST /divisions | Create a division |
| Collection Partner API | POST /managed-cases | Create a case as managing partner |
| Collection Partner API | POST /ingestion-jobs | Enqueue a batch of ingest events |
| Collection Partner API | POST /cases/{id}/chats | Send a chat message (see variant below) |
| Referral Partner API | POST /clients/{externalTenantId}/kyc-verification | Submit KYC verification |
| Referral Partner API | POST /cases/{caseId}/files | Upload 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
422conflict. - 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
externalTenantIdreturn the existing client's current status rather than creating a duplicate. - The response code tracks onboarding progress (
201fully onboarded,202onboarding incomplete,409when 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:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"event": "case.created",
"timestamp": "2026-01-15T10:30:00Z",
"data": { ... }
}
- Extract
idfrom the payload. - If you've already processed that
id, return200 OKwithout re-acting. - Otherwise process the event and record the
id.
See Webhooks for delivery and retry mechanics.
Safe retry checklist
| Operation | How to retry safely |
|---|---|
| Any supported write endpoint | Attach 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 message | Reuse the same Idempotency-Key within 24h for identical retries |
| Process inbound webhook | Deduplicate 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.