How do you implement microservices communication patterns?
Async APIs and webhooks: when the work takes longer than the request
Do not keep the connection open. Return 202 with a job the client can poll, or send a webhook to whoever needs to know and handle every delivery problem.
For this question · chapter 3 of 3
Webhooks invert the call
- Returning 202 with a job to poll is for your own long-running work. With a webhook, you become the caller, because someone else needs to know that something happened. Their server, the consumer, gives you a URL to POST to, /hooks/orders, and gets a signing secret back from you. The signing secret is the only thing that lets the consumer tell your calls apart from anyone else's.
- A webhook brings back every problem that the job resource (the job the client polls) let you avoid, but with the roles reversed. A payment succeeds, so your API becomes the client and sends a delivery to the consumer's URL. The delivery includes the event id evt_9f3 and an HMAC signature of the body. This time, the consumer has to handle those problems.
- Anyone can send a POST to a public webhook URL, so only the signature proves that a delivery came from you. The consumer computes the same signature with the shared secret, so the consumer accepts and records the event evt_9f3. A forged POST from somewhere else has no signature at all, so the consumer returns 401. Compare the received and recomputed signatures in constant time: the check takes the same time whether the two signatures match or not.
- Their server records this delivery, then fails with a 500. So their outage becomes work for your retry queue, which tries again after 1 second, then 5, then 25. Exponential backoff multiplies the wait after each failure, so your retries do not cause a second outage on their server. After six attempts, the delivery becomes a dead letter: your queue sets it aside and stops retrying it.
- At-least-once delivery is the guarantee you can actually make. It means the consumer sometimes gets the same event twice. Here the retry arrives after the consumer's first write already worked, so the consumer now has the same event id twice. No amount of care on your side removes the risk of a duplicate.
- An idempotent consumer applies each event only once, no matter how many times the event arrives. The consumer's only protection is the set of event ids it has already seen, in practice one table with a unique column. The table already holds evt_a02, so the consumer drops the second copy and applies only 2 of the 3 deliveries. Say the term idempotent consumer before the interviewer does.
- Treat the event as a doorbell: a signal that something changed, not the new data itself. One retry makes order.created arrive after order.updated. So the consumer ignores the data in both payloads and fetches /orders/88, which returns shipped. The current state is the same whichever event arrives first. So fetching the current state means the order the events arrive in no longer matters.
Returning 202 with a job to poll is for your own long-running work. With a webhook, you become the caller, because someone else needs to know that something happened. Their server, the consumer, gives you a URL to POST to, /hooks/orders, and gets a signing secret back from you. The signing secret is the only thing that lets the consumer tell your calls apart from anyone else's.
For your own long work, return 202 with a job resource and a Retry-After header, because the server sets the poll interval. Give a failed job its own status and error body. Send a webhook when someone else needs to know, or stream when they need each event as it happens. Sign every delivery and retry with exponential backoff into a dead letter queue. At-least-once delivery sends some events twice, so make the consumer idempotent on the event id.
© LearnThatStack - diagrams may not be republished without permission.
Want a quick review of the fundamentals? See the API Design cheatsheet.
90 of 110 API Design answers are in Pro.
Full answers, code samples, and AI explanations that go simpler or deeper. Cancel anytime.
- Full answers + code
- AI explanations, simpler or deeper
- 1,000 AI credits / month
- Cancel anytime