# Idempotency Key (Safe POST Retries)

> An Idempotency-Key header lets a client safely retry a POST: the server stores the first result and replays it. Learn the protocol, edge cases and conflicts.

Source: https://howhttpworks.com/glossary/idempotency-key
Last reviewed: 2026-10-04

> **TL;DR:** `POST` is not idempotent, so a retry after a timeout can create a duplicate. An idempotency key makes it safe: the client sends a unique key, the server stores the first result under it and replays that result on repeats.

An idempotency key is a unique identifier, sent in an `Idempotency-Key` header, that lets a server recognize repeated attempts at the same logical operation and execute it only once. It gives non-idempotent methods like `POST` the retry safety that [idempotent](https://howhttpworks.com/glossary/idempotent) methods like `PUT` have by definition.

## The exchange

```http
POST /v1/payments HTTP/1.1
Host: api.example.com
Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324
Content-Type: application/json

{"amount": 4200, "currency": "usd"}
```

The connection drops before the response arrives. The client retries with the same key and the server returns the stored response, not a second charge:

```http
HTTP/1.1 201 Created
Content-Type: application/json
Idempotent-Replayed: true

{"id": "pay_123", "status": "succeeded"}
```

The `Idempotent-Replayed` header is a convention some APIs use, not part of the draft.

## What the server has to do

1. Look up the key before doing any work.
2. If it is new, record the key with a fingerprint of the request, process it, and store the status and body.
3. If it exists and the request matches, return the stored response.
4. If it exists but the payload differs, reject it (the draft suggests `422`).
5. If the first request is still running, reject with `409 Conflict` rather than running twice.

## Non-obvious facts

- **Store the result, including failures.** Stripe saves the status and body of the first request for a key, even if it was a 500. Re-running a failed operation under the same key would defeat the point.
- **Keys expire.** Stripe prunes keys after at least 24 hours; document your own retention so clients know how long a retry is safe.
- **The check and the write must be atomic.** A unique database constraint on the key, not a read-then-write, is what stops two concurrent retries from both proceeding.
- **Scope keys per client.** Otherwise one tenant's key can collide with another's or leak a stored response.
- **The key identifies the operation, not the attempt.** Generating a new key on every retry silently disables the protection.

## Go deeper

- [Idempotent](https://howhttpworks.com/glossary/idempotent)
- [POST method](https://howhttpworks.com/methods/post)
- [PUT method](https://howhttpworks.com/methods/put)
- [409 Conflict](https://howhttpworks.com/status-codes/409)
