Technical

Idempotency key

Idempotency key — guarantee that an API request runs exactly once

A mechanism by which a client sends a unique identifier with a mutating API request (POST), ensuring that on a network retry the operation will be executed only once on the server side. A critical best practice for payment APIs.

Definition

The idempotency key is a unique identifier that a client sends with a mutating request (typically a POST) to ensure that, on a network retry, the operation will be executed only once.

The server stores the key; if the same request comes back, it returns the previous response without replaying the operation. It is a critical best practice for any payment API, where a double execution means a double payment, double billing or corrupted state. Stripe, Bridge, Adyen, Mollie, AWS and many others require or support it.

Why it is critical

On the network, uncertainty is the rule: a request can time out without your knowing whether it was received, processed, or whether a retry will duplicate the operation. Without an idempotency key, you cannot retry safely; with one, the client can retry with confidence, and the server returns the stored response if the operation has already happened.

How it works

Format and best practices

  • Generation: UUID v4 (random), ULID (time-sortable, debug-friendly), or a deterministic business hash (SHA256(user_id + transaction_ref)).
  • Header: Idempotency-Key: 4a7f3b80-fe5c-4d12-9c7e-83b3a4ab9b1c. The IETF has published a draft, but the convention was adopted well before it was made official.
  • Cache duration: 24h at Stripe and Bridge, 1h at Mollie. Recommended: 24 to 48h for payments, 7 to 30 days for asynchronous flows. Beyond that, a retry would replay the operation.
  • Scope: per merchant / per account (recommended), rather than global (collision risk).

Different payload, same key

If the same key is sent with a different payload, the server should reject the request (409 Conflict), as Stripe and AWS do. This prevents a payment €200 from being silently ignored after a payment €100 carrying the same key.

Use cases

  • Payments: POST /payments, /transfers, /charges, /refunds — always.
  • Resource creation: POST /customers, /subscriptions — to avoid duplicates.
  • Expensive operations: POST /kyc, /loans/disburse.
  • Unnecessary on: GET (idempotent by nature), PUT with an explicit ID, DELETE.

Idempotency-Key vs other techniques

TechniqueGuaranteeComplexity
Idempotency-Key (header)Exactly-once per callLow on the client side
Unique transaction tokenExactly-once per business transactionMedium
Optimistic DB lockExactly-once per resourceMedium
Saga + commandsExactly-once per flowHigh

The Idempotency-Key works at the API level: it does not protect against a business bug (a user who pays twice via two different buttons generates two distinct keys).

Server-side implementation

Typical storage: Redis (native TTL, key idempotency:{merchant}:{key}), DynamoDB for scalability, or PostgreSQL at low volume.

def handle_payment(req):
    key = req.headers.get("Idempotency-Key")
    if not key:
        return create_payment(req)
 
    cached = redis.get(f"idempotency:{merchant}:{key}")
    if cached:
        return cached
 
    with redis.lock(f"idempotency-lock:{merchant}:{key}", ttl=30):
        cached = redis.get(f"idempotency:{merchant}:{key}")
        if cached:
            return cached
 
        response = create_payment(req)
        redis.setex(f"idempotency:{merchant}:{key}", 86400, response)
        return response

The lock is necessary if two requests carrying the same key arrive at the same time (a retry race condition), to avoid a double execution in parallel.

Behavior on failure

  • Client timeout, server success: retry with the same key → cached response (success).
  • 5xx error: most providers replay (a 5xx is not considered final).
  • 4xx error: retry with the same key → the cached 4xx response is returned.

What the Idempotency-Key is not

  • Not a distributed transaction: it does not guarantee cross-service consistency.
  • Not a business safeguard: two clicks on two buttons generate two keys.
  • Not a replacement for authentication: it is complementary.
  • Not required on GET / PUT: natively idempotent.
  • Not (yet) a formal standard: an IETF draft, but a near-universal convention.

In the PSD2 / Open Finance ecosystem

  • Berlin Group: recommends X-Request-ID (equivalent).
  • STET: the X-Request-ID header.
  • OBIE UK: x-idempotency-key.
  • Acquiring PSPs (Stripe, Adyen, Mollie): universally adopted.
  • Webhooks: the key is also crucial on the receiving side, so as not to process the same event twice.

Concrete examples

  • Stripe: the Idempotency-Key header, a 24h cache, rejection of a different payload — the industry standard.
  • Bridge: Idempotency-Key + X-Request-ID depending on the endpoint.
  • Mollie: Idempotency-Key, a 1h cache.
  • AWS: the client-token parameter.
  • Real-world case: a merchant without an idempotency key retries after a timeout → double debit on 0.5% of payments → tens of thousands of euros in refunds and lost trust.
  • UI pattern: generate the key on click and persist it in the form state — a re-click 5 seconds later reuses the same key, with no double charge.

Sources

Go deeper

From the blog

Longer reads to go beyond the definition.

April 7, 2026
technical

Technical architecture of a PSD2 API: what to know before writing the first line of code — Read more

A technical overview of PSD2 for CTOs, developers and technical PMs: API standards, security (mTLS, eIDAS certificates), OAuth2, SCA flows, consent management, implementation pitfalls.

April 6, 2026
regulation

Understanding PSD2: what it changes for your customers and your business — Read more

Everything an executive, a product manager or a business team needs to understand about PSD2. Players, consent, opportunities, without technical jargon.