Networks fail, processes restart, people click twice. Every write in the API is designed so that repeating it is safe.
Events: the key is the event
For POST /v1/events the idempotency key is, in order: the Idempotency-Key header, the event's own id, or a key derived from the event (order.paid:ORDER-1001). So the same order sent twice is the same event: the second call returns the original with replayed: true and nothing is enqueued again. In a batch, each event's id is its key.
Even if an event were processed twice, ledger entries carry their own keys — a reprocessed event cannot double-credit.
Other writes: send Idempotency-Key
Enrol, redeem, reserve, adjust, webhook endpoints — send a header with a value that is unique per intent (an order id, a cart id, your own request id):
POST /v1/members/M-6EFEAB76DFCC/redemptions
Idempotency-Key: pos-7781-redeem| You send | You get |
|---|---|
| A new key | The request runs. |
| The same key, same body | The original response, with Idempotent-Replay: true. Nothing runs again. |
| The same key, different body | 409 conflict — a key collision or a retry bug on your side. |
| The same key while the first call is still running | 409 conflict with reason: in_progress and Retry-After — wait and retry. |
| A key whose first call failed (4xx/5xx) | The request runs again. Failures are never remembered. |
Keys are kept for 30 days.
What to retry
| Response | Retry? |
|---|---|
| Network error, timeout | Yes, same key. |
429 | Yes, after Retry-After. |
500, 502, 503 | Yes, same key, backing off. |
409 in_progress | Yes, after Retry-After. |
Any other 4xx | No — it is a fact about the request. Retrying unchanged gets the same answer. |
The SDKs do all of this for you and never repeat a write that has no key.