Idempotency keys: safe retries for POST
Networks fail in the worst place: the server created the payment, but the response never reached the client. The client can't tell 'failed' from 'succeeded, answer lost', so it retries, and a plain POST charges twice.
An idempotency key fixes this. The client generates a unique key (a UUID) per logical operation and sends it in a header, Idempotency-Key. The server stores the key with the result of the first request. A retry with the same key gets the stored response instead of running the operation again. If the same key arrives with a different body, the server rejects it (commonly 422), because that's a client bug, not a retry. Keys are kept for a limited time, such as 24 hours.
Stripe accepts an Idempotency-Key header on POST requests and returns the saved result for repeats. An IETF draft proposes the same header for any HTTP API.
In code: HTTP · Python
POST /payments
Idempotency-Key: 8e0f3c1a-6b0e-4a8e-9f6c-2d1b7a1c9e55 # Stripe-style
{"order_id": "42", "amount": 1999, "currency": "EUR"}
→ (timeout: the client never sees the answer)
POST /payments # retry, same key, same body
Idempotency-Key: 8e0f3c1a-6b0e-4a8e-9f6c-2d1b7a1c9e55
→ 201 Created # the stored response; no second charge# server side, simplified (Flask-style)
stored = {} # in production: a database table with a unique key
def create_payment(key, body):
if key in stored:
if stored[key]["body"] != body:
return 422, {"title": "Idempotency key reused with a different body"}
return stored[key]["status"], stored[key]["response"]
response = charge(body) # runs once per key
stored[key] = {"body": body, "status": 201, "response": response}
return 201, response- In production the key is saved in the same database transaction as the payment, so a crash can't leave one without the other.
- Two requests with the same key arriving at the same moment must not both run: lock on the key or rely on a unique constraint.
A client retries POST /payments with the same Idempotency-Key after a timeout. What should the server do? (Choose two.)
Select 2, then check.