Failure pattern · 1 documented incident

Duplicate action after an ambiguous result

A request times out, the caller assumes it failed and retries, and the money moves (or the order ships) twice.

See it happen

Watch how this failure unfolds, then switch to the same situation with the safeguard in place. Click any step to replay it.

Scroll the diagram sideways to see every system.

12 / 12 s

Ana is refunded twice, and every further retry would pay again.

What goes wrong:

  1. The agent asks the payments API to refund Ana $50.
  2. The money moves, but the reply is lost on the way back.
  3. Assuming the refund failed, the agent sends it again.
  4. Ana is refunded twice, and every further retry would pay again.

With the safeguard:

  1. Each refund carries a unique idempotency key.
  2. The reply is lost again.
  3. The retry reuses the same key, so the API recognises it and returns the first result.
  4. One refund, no matter how many retries.

What it is

A request that changes something (a payment, an order, a message) times out. The caller can’t tell whether it worked, assumes it didn’t, and sends it again. The first one had gone through, so now it happens twice.

How it happens

  • Your system calls a payments, messaging or provisioning API that it doesn’t control
  • The network drops or a gateway returns 502/504 after the other side has already done the work
  • Retry logic resends the request with nothing that marks it as the same request

What it causes

  • Customers charged, refunded or paid twice
  • Orders shipped twice, tickets or seats allocated twice
  • Your ledger and the provider’s records drift apart

Warning signs

  • Two transactions for what the customer meant as one action
  • Bursts of client timeouts immediately followed by success responses
  • Order status in your database disagrees with the payment provider

How to prevent it

Make every consequential call idempotent. Send a client-generated idempotency key with each state-changing request, treat timeouts as "unknown" rather than "failed", and check the remote status before retrying.

  • Every request that moves money or changes state carries a unique idempotency key
  • A timeout is treated as “unknown”, not as “failed”
  • Before retrying a consequential action, the system checks what actually happened

Key ideas:Idempotency key

Check your system against the full launch checklist

Real incidents with this pattern

Documented incidents first. Each links to its public source.

Open in the database
Documented incidentPublished 2017-02-22

Design reference: idempotency keys make retried payment API calls safe (Stripe)

Stripe explains that a client which cannot tell whether a request succeeded (for example after a network failure) needs to retry safely, and that without protection a retried charge could double-charge a customer.

Safeguard: Attach idempotency keys to state-changing requests

Duplicate action after an ambiguous resultstripe.com
16 security advisories and illustrative scenarios with this pattern
Illustrative scenarioDate not recorded

JWT Secret Key Confusion & Privilege Escalation

Backend accepted HMAC-SHA256 tokens signed with public RSA key, allowing attacker to forge administrator tokens.

Duplicate action after an ambiguous result

In the standards

CollectSEA's mapping; not an official determination by the standards bodies.