Duplicates and Missed Calls
One Tuesday the office's tool records ride rd_7c29 as completed twice, and the day's count comes out one trip too high. A week later the opposite happens: a ride ends at Old Market and no delivery ever arrives at all. Vera writes to Jonas expecting an apology.
She gets a link to the documentation instead, and the page says, in one line she had skimmed past: deliveries are at-least-once; order is not guaranteed. Neither Tuesday was a malfunction. Both were the published contract working exactly as written, and this page is about consuming events the way that contract always meant.
Why Duplicates Happen
Follow the Tuesday message all the way through. Tandem sends the delivery. The office's endpoint receives it, records the completed ride, and answers with a 200 to say so. That answer then dies somewhere on the way back — a dropped connection, a network hiccup, the kind of thing that happens to a small fraction of everything.
Now look at it from Tandem's side, which knows only what it heard: nothing. It cannot tell "the office never got it" from "the office got it and the reply was lost". Having promised to deliver the news, it does the only responsible thing available and sends again.
You have stood on the other side of this exact ambiguity. Chapter 7 left Vera's finger hovering over the up-arrow after a POST timed out, unable to tell whether the reservation had been made. This is that same gap, seen from the sender's chair, and Tandem resolves it the way the chapter recommended for a retryable request: assume it did not land, and send it once more. The duplicate is not a bug. The duplicate is the retry working.
{"event": "ride.completed", "delivery_id": "dlv_88f1",
"sent_at": "2026-08-27T17:52:04Z",
"data": {"id": "rd_7c29", "status": "completed"}}
{"event": "ride.completed", "delivery_id": "dlv_88f1",
"sent_at": "2026-08-27T17:52:39Z",
"data": {"id": "rd_7c29", "status": "completed"}}
Two arrivals, thirty-five seconds apart, carrying one identifier between them. That repeated identifier is the whole solution, and the section after next uses it.
Why Gaps Happen
The silent ride is the same coin's other face. A provider that promises to deliver at least once still has to stop trying eventually, so every webhook contract comes with a retry schedule: how many further attempts it will make, and how far apart. Tandem's is in the same documentation paragraph as the at-least-once line, right where the rate limits and the version policy live.
If the receiving endpoint is down for longer than that schedule covers — an afternoon of maintenance, a tool that quietly stopped, a hosting bill nobody paid — the attempts run out and the event is simply gone from the office's inbox. It still happened. Tandem still knows about it. Nobody told the office, and nobody will.
Promises have edges, and this is where the webhook's edge is. A message-based system will occasionally lose a message, and a design that assumes otherwise breaks silently and produces a number that is wrong without complaining.
The Consumer's Two Disciplines
The good news is that both problems are handled by two habits, and neither requires writing a program to understand or to insist upon.
The first is deduplication: before acting on a delivery, ask whether this one has been seen already. Every delivery carries an identifier — Tandem calls it delivery_id, other providers use an event id, and the documentation names the field. The receiver keeps a list of the ones it has handled, and an identifier already on the list means the message gets acknowledged and otherwise ignored. Two arrivals, one fact.
The second is reconciliation: on a schedule, go and ask the source. Once a day, the office fetches the recent rides with an ordinary GET and compares them against the events it received. Anything in the list that never arrived as an event gets picked up then. That is polling from two pages ago — demoted from main mechanism to safety net, which is the role it plays best. Once a day at that job is worth more than every minute at the other.
The everyday version is a parcel delivery. Nobody signed, so the driver leaves a card and comes back tomorrow, which occasionally means two knocks for one parcel. And if your doorbell was broken all week, the depot's own list of what was sent is where you find out what you missed. The depot list exists precisely because doorbells fail.
Events Notify, the Resource Is the Truth
Underneath both disciplines is one idea, and it is the one to carry out of this chapter. An event is a notification that something happened. The resource behind the API is what is true. When the two disagree, the resource wins, always.
Two consequences fall out of that immediately. Arrival order tells you nothing reliable: a retried old event can land after a newer one, so the occurrence time inside the payload is the story, not the order deliveries showed up in — and where even that is ambiguous, the resource behind the API settles it. And nothing important should be believed only because a message said so — the fetch that follows an event is not decoration, it is the check.
Build the Thursday report on that footing and Tuesday's twin deliveries become one logged ride, and the silent one is caught at the daily reconciliation. Both endings are unremarkable, which is exactly what a system that expected them looks like from the outside.
- "Duplicates mean the provider is buggy." At-least-once is the published contract, stated in the documentation before anything went wrong. Delivering exactly once over an unreliable network is, in the general case, not something anyone can offer you.
- "Events arrive in the order they happened." A retried old event can land after a newer one, so arrival order is not a timeline. The timestamps inside the payloads are what tell you the sequence, and they are there for that reason.
- "Verified signatures make deliveries reliable." A signature proves who sent this particular message. It promises nothing about how many times it will arrive, or whether it will. Authenticity and delivery are separate clauses of the same contract.
- "If I reconcile daily, I do not need to dedupe." Reconciliation catches what never arrived; it does not un-count what arrived twice. The two disciplines cover opposite failures and neither substitutes for the other.
- Dedupe and reconcile are the difference between an event-driven report you can defend in a meeting and one that quietly double-counts a Friday. Neither needs code to understand, only to be insisted on.
- "What is this webhook's delivery guarantee, and what is its retry schedule?" completes your set of questions for any provider's documentation — the fourth alongside authentication, rate limits, and versioning.
Knowledge Check
Why does the same event sometimes arrive twice?
- Because the ride was recorded as ending on two separate occasions
- Because the acknowledgment was lost, so the provider sent it again
- Because the endpoint was subscribed to the same event more than once
- Because the receiving tool requested the delivery a second time
What makes deduplication possible at all?
- Each delivery carries an identifier the receiver can remember
- The second copy always arrives with a later timestamp inside it
- The provider marks retried deliveries so they can be dropped
- Duplicates arrive within seconds, so a short pause filters them
A ride ends and no delivery ever arrives. What catches it?
- Signature verification, which flags the missing delivery
- The provider's retries, which continue until the endpoint answers
- A scheduled fetch of recent rides, compared against what arrived
- Deduplication, which notices the identifier that never appeared
An old event arrives after a newer one. What should the receiver trust?
- The order they arrived in, since that is the order they happened
- The timestamps in the payloads, and the resource behind them
- Neither, and both should be discarded as unreliable messages
- The one delivered most recently, as the freshest information
You got correct