Usage

Client-Side

Clients include an Idempotency-Key header with a UUID v4 value on any request they want to be idempotent:

POST /api/orders HTTP/1.1
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

{"product_id": 1, "quantity": 2}

Generating Keys

The key must be a valid UUID v4. Most languages have built-in support:

const key = crypto.randomUUID();
$key = \Ramsey\Uuid\Uuid::uuid4()->toString();
import uuid
key = str(uuid.uuid4())

When to Use a New Key

Generate a new key for each unique operation. Reuse the same key only when retrying the exact same request.

Server-Side Behavior

First Request

When a request with a new idempotency key arrives:

  1. The request is processed normally through your controller
  2. The response is cached (keyed by the UUID)
  3. The Idempotency-Key header is included in the response

Repeated Request (Same Key, Same Body, Same Endpoint)

When the same key is sent again with the same body to the same endpoint:

  1. The cached response is returned immediately — your controller is not called
  2. The response includes an Idempotent-Replayed: true header
  3. Status code, body, and headers match the original response

Requests Without a Key

If a request doesn't include the Idempotency-Key header, the middleware is skipped entirely. The request proceeds as normal with no caching.

GET Requests

GET requests are never idempotent by default (they're already safe to retry). The middleware only applies to the HTTP methods defined in config — POST, PUT, PATCH, and DELETE by default.