Skip to content

idempotency ​

idempotency makes POST and PATCH requests safe to send again. The response of the first request with an Idempotency-Key header is stored, and later requests with the same key get that response without reaching the upstream. Clients can retry after a timeout without, for example, charging a card twice.

yaml
server:
  http:
    middlewares:
      payments_idempotency:
        idempotency:
          required: true
          ttl: 24h
    routers:
      payments:
        path: /payments/*
        middlewares:
          - auth
          - payments_idempotency
          - backend
sh
curl -X POST https://api.example.com/payments \
  -H "Idempotency-Key: 5f1c2a8e-4b7d-4c1e-9f3a-2d6b8e0c7a41" \
  -d '{"amount": 100}'
FieldDefaultDescription
headerIdempotency-KeyRequest header holding the key.
methodsPOST, PATCHMethods that use keys. Other requests pass unchanged.
requiredfalseReject requests of methods without a key with 400.
ttl24hHow long a response is stored.
lock_timeout1mHow long a key stays locked while its first request runs. A request that runs longer may run twice.
scopeheader:AuthorizationSeparates the keys of different clients, so one client cannot read the response of another. Same keys as rate_limit: ip, header:<name>, cookie:<name>, …
trusted_proxiesCIDRs whose forwarding headers are used for the ip scope.
statuses200-499 without 408, 409, 425, 429, 499Stored statuses, e.g. [200-299].
max_body_size1MBLargest request body hashed and response body stored. Larger requests get 413.
status_headerIdempotent-ReplayedSet to true on stored responses. - disables it.
max_size64MBSize of the memory store. The least recently used keys are removed first.
redisShare keys between instances. Same fields as cache Redis, key_prefix defaults to turna_idempotency_.

Behavior ​

RequestAnswer
New keySent upstream; the response is stored when its status is in statuses.
Same key and requestThe stored response with Idempotent-Replayed: true.
Same key, first request still running409 Conflict with Retry-After: 1.
Same key, different method, path, query or body422 Unprocessable Entity.

5xx responses and 408, 409, 425, 429 and 499 are not stored by default, so the request can be sent again with the same key and succeed later. A response is also not stored when the client went away before it was complete, since the client may not have seen it.

Replayed responses keep the status, body and headers of the first one, except headers that belong to a single response: Set-Cookie, Date, X-Request-Id, Traceparent, Tracestate and Server-Timing.

Each lock holds a random token. When a request runs longer than lock_timeout and another request takes the key, the first one can neither release nor overwrite the second one's lock.

When the store is unavailable, requests go to the upstream without idempotency. With more than one Turna instance use redis, otherwise each instance has its own keys.

Put idempotency after authentication, so scope sees the Authorization header and unauthenticated requests do not lock keys.