Skip to content
← All articles
The retry storm that almost paid a merchant twice
Insight· 5 min read

The retry storm that almost paid a merchant twice

Thursday 16:47. A merchant payout. The client timed out, then fired again. Then again. Ninety seconds from paying the same person three times.

Finance never opened a ticket. Support never called the merchant. The only person who noticed was an engineer with coffee, reading a log line three requests wide.

That is the version that shipped. The polite title says twice. The logs said three.

The callback that said SUCCESS lied with a word. This one lied with silence.

Thursday, 16:47

Payout PAY-33108: 85,000 XOF, one merchant, one order. The mobile money provider does not go down. It goes slow — four seconds, then five, then eight. Usual time is a few hundred milliseconds. The client's timeout is three seconds.

At 16:47:03 the client decides the first request failed.

It had not failed. It was still in flight.

The client fires the same payout again. Then again.

What the client believed vs what was true

What the client sawWhat was actually happening
Timeout at 3 secondsThe first transfer was still crawling (5–8s)
No responseThe provider was slow, not down
Safe to retryThe first request had already arrived

Within ninety seconds the same payout command had been submitted three times. On the provider side, all three would have succeeded — three separate transfers, same amount, same recipient, one sale.

Finance would not have seen it that afternoon. They would have seen it in an end-of-day file — or days later — with no trail back to a three-second timeout.

The idea in one sentence

Idempotent means: send the same money command twice, the money moves once.

Not “the API has a header.” Not “we log duplicates.” The economic fact happens once, even when the network answers with silence, a reset, or a late OK.

The interview question is not whether you know the word. It is: what is the key made of, and can a retry change it?

The one check that held

The payout endpoint did not accept a random UUID. It required a key hashed from merchant + order + amount — the business intent, not a ticket the retry loop could mint again on every attempt.

All three retries carried the same key. The server saw calls two and three as duplicates of a command already in flight, returned the original response, and never issued a second transfer.

How you mint the keyWhat a retry storm does
New UUID on every attemptThree transfers. Finance incident. Call to the merchant.
Stable hash of the business intentOne transfer. A noisy log. Nobody in finance ever hears about it.
WhoWithout that keyWith it
FinanceThree credits for one sale, found at reconciliationNothing to explain
SupportA merchant asking why they were paid three timesNo ticket
EngineeringAn incident reviewOne log line, noticed the next morning

No refund. No awkward call. Just a slightly noisy log that a bored engineer read with coffee.

The Friday that would have happened

If the key had been a fresh UUID on each retry, Thursday's payout would have landed three times. Friday's settlement file would have been too right — extra credits, not missing rows. Product would have called it a win. Finance would have called it a leak. Someone would have been on the phone with the merchant, explaining a refund for money they had already spent.

Same rails as the SUCCESS callback. Different lie: not a word, a gap.

The lesson was not “add retries carefully.” It was that idempotency has to be derived from the business intent of a command, not from a client-side identifier that a retry can accidentally reset.

Same rule as in scalable fintech systems: ten retries must equal one economic intent. If the key can change between attempts, the safety net was never there.

Bottom line

Idempotency is not a header. It is a business key that must survive every timeout, every retry, and every eager client.

The next diagram

A word lied. Silence lied. The next lie is a box that stops too early.

Thursday's key saved one payout. A gateway has to hold that same rule on every hop: auth, capture, settlement, ledger, webhook, payout. Each can timeout. Each can retry. Each can look finished while the money is still in flight.

Interview diagrams stop at “call Visa.” The money is lost in the gaps.

From card tap to merchant payout →

Want to apply this on your platform?

Describe your tech landscape and constraints — I'll offer straightforward advice if there's a technical match.