Idempotent Requests
Any POST, PUT, or PATCH can be made idempotent by sending an Idempotency-Key header (X-Idempotency-Key also works). It's opt-in — without the header, a retried request is just a new request.
Idempotency-Key: 3f29a1c4-8b7e-4e2a-9c1d-6a5b7e8f9a0b
Any unique string works — a UUID is a reasonable default. Reusing the same key on the same route with the same caller is what gives you the safety; reusing it across different routes is fine, since keys are scoped per endpoint too.
What happens on a retry
- The original request already finished — you get back the exact same response (status, body, headers) that the first call returned, with an added
X-Idempotent-Replay: trueheader. A create call that already succeeded won't create a second payment. - The original request is still in flight — you get a
409with{"error": {"code": "IDEMPOTENT_REQUEST_IN_FLIGHT"}}. This means two requests with the same key arrived close together; wait and retry rather than treating it as a failure. - The original request failed with a server error (5xx) — nothing is cached, so a retry with the same key runs fresh. Only successful and client-error responses (2xx–4xx) are replayed.
How long a key is remembered
24 hours. A retry an hour after a network timeout, or even most of a day later, is still recognized and served the original result rather than creating a duplicate.
Where it matters most
Always send an idempotency key on POST /payments (creating a payment intent), POST /payments/:reference/capture, and POST /payments/:reference/refunds — these are exactly the calls you want to be safe to retry after a timeout or a dropped connection, since each one moves real money.