Idempotency-Key and the guess goes away.
Idempotency-Replayed: true. One contact exists, not two.
Where it works
Every write endpoint — everyPOST, PATCH and DELETE under /v1. The header is optional; omit it and the endpoint behaves exactly as it always did.
Reads ignore it.
How to choose a key
Derive it from the thing you are acting on, not from the attempt:What each outcome means
First attempt — the request runs
First attempt — the request runs
The key is reserved before any work happens, then the response is recorded against it.
Retry, same body — the response is replayed
Retry, same body — the response is replayed
You get the original status code and body, plus
Idempotency-Replayed: true. No
second message, no second charge.Retry, different body — 422
Retry, different body — 422
Concurrent retry — 409
Concurrent retry — 409
Server errors release the key
A5xx is not recorded. The key is freed so your retry actually runs — which is what you want, because the original attempt may never have completed.
Responses that are not JSON, such as a media download, are also not recorded.
How long a key lasts
24 hours from first use. After that the key is forgotten and reusing it starts a fresh request.Message-level deduplication
POST /v1/messages and POST /v1/events also accept an idempotency_key in the body. That is an older, narrower guarantee: it deduplicates the message or event itself in the database, and it is enforced permanently rather than for 24 hours.
The two coexist. Use the header for transport safety; use the body field when you need a permanent guarantee that one business event produces one message, forever.
Retry recipe
For a robust integration:- Generate one key per logical write (for example
order-1042-shipped). - Send
Idempotency-Key: <key>on the request. - On network error, timeout, or
5xx: retry with the same key and body. - On
Idempotency-Replayed: true: treat as success — it was a replay. - On
409(in progress): wait briefly and retry with the same key. - On
429: back off; see rate limits.
X-Request-Id is a correlation id for support logs. It is not an idempotency key — do not reuse it to prevent double-sends.