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.

Backend Engineerpythonflaskidempotency

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

  1. Client generates a UUID before the first attempt and sends it as Idempotency-Key.
  2. Server processes the request, stores (key -> response), and returns the response.
  3. 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

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

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

Return the Correct HTTP Status CodeBackend projectRepair Broken API PaginationBackend projectAdd CORS Middleware to an Express APIBackend projectBackend roadmapStep by step to hiredBackend interview questionsSTAR answersAll Backend projectsProjects hub

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 →