Designing an Interface to Call
The Second Call Is Not a Mistake
Last timeSaying What Went Wrong
A caller who never saw your answer has to ask again. Making that safe means a key they generate, a record you keep, and a decision about how long to keep it.
Why the second call happens
Somebody charges a card. Your service takes the request, charges the card,
writes the record, builds the response, and the connection drops before the
response arrives. The caller waits, times out, and sees nothing.
Now put yourself on their side. They know they sent a request. They do not
know whether it arrived. They do not know whether it was processed. A timeout
looks exactly the same whether the work happened or did not, and there is no
way for them to find out by looking at their own side of the wire.
They have two options and both are wrong. Give up, and a customer may have
been charged for an order that does not exist. Send it again, and a customer
may be charged twice. The second call is not a caller mistake. It is the only
remaining move for somebody who was denied an answer, and an interface that
punishes it is an interface that has pushed its own problem outwards.
| step | moment | what the caller knew | what the service knew | the customer | what happened |
|---|---|---|---|---|---|
| 1 | request sent | waiting | nothing yet | card not charged | Ordinary. The caller is holding an open connection and a timer. |
| 2 | card charged | still waiting | charged, record written | charged once | The work is done and committed. Everything after this point is about the answer, not the work. |
| 3 | connection drops | timeout, outcome unknown | response sent successfully | charged once | The service believes it succeeded. Its logs will say so. The caller has nothing. |
| 4 | caller retries | trying again | a second identical request | charged twice, unless | The last column is the whole subject of this lesson. Whether it says once or twice depends entirely on what the service does next. |
A key the caller generates
The fix is to let the caller name the attempt. They generate a value before
they send anything, attach it to the request, and attach the same value to
every retry of that same intent. The service uses it to recognise the retry.
The important part is where the value comes from. It has to come from the
caller, and the reason is a question only the caller can answer: is this the
same intent as before, or a new one? Two requests to charge forty-two dollars
to the same card for the same order might be a retry of one payment or a
customer who genuinely bought the same thing twice in a minute. The contents
are identical. The intent is not, and nothing in the request distinguishes
them.
This rules out the tempting shortcut of hashing the request body and treating
matching hashes as duplicates. That design refuses the legitimate second
order, which is a worse failure than the one it fixes, because it is silent
and the customer is simply told no.
POST /payments
Idempotency-Key: 8c1f0e5a-5a3d-4e71-9b24-2f8d6a0b7c33
{amount: 4200, currency: cad, order: ord_7781}
first call 201 Created, payment pay_4Q2k, key recorded
retry, same body 200 OK, payment pay_4Q2k, replayed from the record
retry, in flight 409 Conflict, the first call has not finished
same key, new body 422 Unprocessable, that key means something else
recorded against the key
key the value the caller sent
request_digest a hash of method, path and body
state in_progress or complete
status_code 201
response_body the whole first answer, byte for byte
created_at 2026-10-09T14:12:08ZTwo properties make a key work. It must be unique per intent, which in
practice means a random identifier rather than anything derived from the
contents. And it must be generated before the first attempt and held by the
caller across retries, which means it has to survive whatever made the retry
necessary, including a process restart on their side.
The lesson stops here
4 more paragraphs to go
You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.
The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.
See the planThe contentsThis is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents