Skip to main content

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: true header. A create call that already succeeded won't create a second payment.
  • The original request is still in flight — you get a 409 with {"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.