# Idempotency Keys for Safe Retries — Rate Limiting, Circuit Breakers & Resilience Patterns

Source: https://www.skillbyai.com/en/resilience-patterns/l-idempotency

> Implement server-side idempotency keys so clients can retry non-idempotent operations.

## Making POST safe to repeat

Retries are only safe for idempotent operations. Creating a payment or an order with `POST` is not idempotent: if the response is lost, the client cannot tell whether the order was created, and retrying might create a duplicate. The fix is an **idempotency key**: the client generates a unique key per logical operation (a UUID) and sends it in a header such as `Idempotency-Key`. The server stores the key with the request fingerprint and the result. If the same key arrives again, the server returns the **stored result** instead of performing the operation again; if the key arrives with a **different request body**, it returns an error; if the first request is still in progress, it returns a conflict or waits. Keys are kept for a defined retention period (often 24 hours or more). Payment APIs such as Stripe popularised this pattern, and an IETF draft describes the `Idempotency-Key` header for HTTP APIs.

## Server-side idempotency key handling

The key record and the business write are tied together so retries return the original result.

```python
import hashlib, json

def create_order(request, db):
    key = request.headers.get("Idempotency-Key")
    if not key:
        return 400, {"error": "Idempotency-Key header required"}
    fingerprint = hashlib.sha256(json.dumps(request.json, sort_keys=True).encode()).hexdigest()

    with db.transaction() as tx:
        row = tx.fetch_one("SELECT fingerprint, status_code, body FROM idempotency_keys WHERE key = %s FOR UPDATE", (key,))
        if row:
            if row.fingerprint != fingerprint:
                return 422, {"error": "key reused with a different request"}
            return row.status_code, json.loads(row.body)      # replay stored result

        order = tx.insert_order(request.json)
        body = {"orderId": order.id, "status": "PLACED"}
        tx.execute("INSERT INTO idempotency_keys (key, fingerprint, status_code, body, created_at) "
                   "VALUES (%s, %s, 201, %s, now())", (key, fingerprint, json.dumps(body)))
    return 201, body
```

## A receipt number at a counter

If you are unsure whether the clerk processed your form, you show your token number. The clerk looks it up and hands you the same receipt instead of processing the form twice.

**Quiz:** A client retries POST /payments with the same Idempotency-Key after a timeout. What should the server do if the first attempt succeeded?

- [ ] Charge the customer again
- [ ] Return 404
- [ ] Ignore the key and process normally
- [x] Return the stored result of the first attempt without charging again

*Answer:* Return the stored result of the first attempt without charging again. The stored response is replayed, making the retry safe.
