How to Implement an Idempotency Key API (Stripe Pattern)
Without an idempotency key, a mobile client that retries a POST over a flaky network creates a duplicate order every time. The Stripe-style Idempotency-Key header fixes this: store the first response keyed by a client-supplied UUID, return it on every retry.
Why retried POSTs cause duplicates
POST is not idempotent by design - calling it twice does the work twice. On unreliable mobile networks, HTTP clients retry aggressively, so a successful order creation that loses its response in transit becomes two orders in the database. Unique constraints catch some cases but they lose the original response, forcing the caller to do a separate GET just to find out what happened.
The standard solution, used by Stripe, AWS, and most payment APIs, is a
client-supplied Idempotency-Key header: a UUID the client generates once for
each logical operation and resends on every retry.
How it works
- Client generates a UUID before the first attempt and sends it as
Idempotency-Key. - Server processes the request, stores
(key -> response), and returns the response. - On a retry with the same key, the server returns the cached response without re-executing the handler.
from flask import Flask, request, jsonify
import uuid
app = Flask(__name__)
IDEMPOTENCY = {} # idempotency-key -> (status, body)
@app.route("/orders", methods=["POST"])
def create_order():
body = request.get_json(force=True) or {}
key = request.headers.get("Idempotency-Key")
# Return the cached response if this key has been seen before
if key and key in IDEMPOTENCY:
status, payload = IDEMPOTENCY[key]
return jsonify(payload), status
# First request - do the real work
order_id = str(uuid.uuid4())
payload = {
"order_id": order_id,
"item": body.get("item"),
"qty": body.get("qty"),
}
if key:
IDEMPOTENCY[key] = (201, payload)
return jsonify(payload), 201
Verify it works
# First POST - creates the order
curl -sX POST http://localhost:8000/orders \
-H 'Idempotency-Key: abc-123' \
-H 'Content-Type: application/json' \
-d '{"item":"widget","qty":1}'
# {"order_id": "b4a7...", "item": "widget", "qty": 1}
# Retry with the same key - same order_id returned, no duplicate
curl -sX POST http://localhost:8000/orders \
-H 'Idempotency-Key: abc-123' \
-H 'Content-Type: application/json' \
-d '{"item":"widget","qty":1}'
# {"order_id": "b4a7...", "item": "widget", "qty": 1} <- identical
Both calls return the same order_id. The second call never touched the
database.
Production considerations
- Use Redis, not a dict - an in-memory dict works for a single process but is lost on restart and doesn't span replicas. Redis with a 24-hour TTL is the standard production store.
- Scope the key to the caller - use
(api_key, idempotency_key)as the composite cache key so two different users with the same UUID don't collide. - Cache the full HTTP response - store status code + body (not just a row ID) so retries get a byte-for-byte identical response.
- Return 422 for key conflicts - if the same key arrives with a different
request body, that is a client bug; return
422 Unprocessable Entityrather than silently returning the cached response for different data.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Reading the Idempotency-Key header and short-circuiting on a cache hit
- Caching the full response (status + body) keyed by the client UUID
- Verifying that repeated POSTs with the same key return the same order_id
FAQ
What is an idempotency key in an API?
An idempotency key is a client-supplied UUID sent as an HTTP header (Idempotency-Key) on a POST request. The server caches the first response under that key and returns the same response on any retry, so the operation is only executed once no matter how many times the client retries.
How do I implement idempotency in a Flask API?
Read the Idempotency-Key header at the top of your handler. If the key exists in your cache, return the stored (status, body) immediately. Otherwise process the request, store the response in the cache keyed by the header value, and return it. Use Redis for production; an in-memory dict is fine for a single-process prototype.
Why use an idempotency key instead of a database unique constraint?
A unique constraint prevents duplicates but raises an error and loses the original response. The client then needs a separate GET to recover. An idempotency key returns the exact original response on every retry - cleaner for the caller and safer over flaky networks.
What is an idempotency key used for?
An idempotency key lets a client safely retry a request without duplicate side effects. The server stores the key with the first result and returns that same result on any retry with the same key - so a double-clicked payment charges once.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →