Idempotency
Networks fail. If a request times out you cannot tell whether the platform processed it. An idempotency key lets you repeat the request safely: it is executed at most once and every repeat returns the same answer.
Every POST accepts an Idempotency-Key header of up to 255 characters.
curl -X POST https://pay.example.com/v1/refunds \
-H "Authorization: Bearer sk_sandbox_YOUR_KEY" \
-H "Idempotency-Key: refund-order-1001-1" \
-H "Content-Type: application/json" \
-d '{"payment_intent": "pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5A", "amount": 2500}'
Rules
- Keys are stored for 24 hours, separately for each API key.
- The platform stores a hash of the request. The same key with the same body replays the stored response (status and body) and adds the header
Idempotent-Replayed: true. - The same key with a different body is refused with
409anderror.type = idempotency_error,error.code = idempotency_conflict. This protects you from accidentally reusing a key for another order. - If the first request is still running when the repeat arrives, the repeat gets
409 idempotency_in_progress. Wait a moment and retry with the same key. - Every response the platform produced for the first request is stored and replayed, including
4xxand5xx. A request that failed after an external effect (for example a bank call) is never silently executed again under the same key. - The same key used on a different endpoint is also a
409 idempotency_conflict. - Requests refused before they reach the idempotency step (
401,403for a missing scope or IP, malformed JSON, a card number in a wrong field) do not consume the key. GETandDELETErequests are naturally idempotent; the header is ignored.
Choosing keys
Derive the key from your own business cause, not from a random value generated per attempt: order-1001-payment, refund-order-1001-1. Then a crash and restart of your worker retries with the same key and cannot double charge. Use a random UUID only when your system has no stable identifier, and store it with the order before you send the request.
Use a new key for a genuinely new attempt, for example when the customer retries with a different card after a decline.
What to retry
| Outcome | What to do |
|---|---|
| Timeout or connection reset (no response received) | Retry with the same key. |
409 idempotency_in_progress |
Retry with the same key after a short wait. |
Any stored response (4xx, 5xx) |
A repeat with the same key returns the same response. Fix the request, then use a new key. |
402 card decline |
Ask for another payment method and use a new key. |
idempotency_in_progress that never ends (your process crashed mid-request) |
The key stays locked until it expires after 24 hours and is never taken over automatically, because re-running could duplicate a payment. Check the state of the payment first, then retry with a new key. |
If you are unsure whether a payment went through, search for it (GET /v1/payment_intents?q=… matches an id, the customer e-mail or a description prefix; metadata[order_id]=1001 matches exactly) or look at the request in Developers → Logs before creating a new one.