unit U3 — 3 of 4
Retry semantics
at-least-once delivery, dedup strategies
Webhook providers guarantee at-least-once delivery, not exactly-once. If your endpoint is slow, errors, or returns a non-2xx, the provider retries — often several times with exponential backoff. So the same logical event will sometimes arrive twice, or five times. Your pipeline must reach the same end state no matter how many copies land. That property is idempotency, and it’s non-negotiable for anything that writes to a CRM, sends an email, or charges a card.
The delivery id from U1 is your dedup key. Before acting, check whether you’ve already processed that id. A durable store — a database table, a Redis SET with a TTL, or the engine’s built-in store — records ids you’ve seen. First sighting: process and record. Repeat sighting: acknowledge with 200 and stop. Returning 200 on the duplicate matters; a non-2xx tells the provider to retry even harder.
Where it breaks: the slow endpoint. If real work runs before you respond, a provider timeout (often 3–5s) triggers a retry while your first run is still going — now two copies race. Respond 2xx immediately, then do the work behind the acknowledgement.
worked example
A dedup gate keyed on the delivery id, backed by a store with a TTL.
export const code = async (inputs) => {
const id = inputs.headers['webhook-id'];
const seen = await store.get(`evt:${id}`); // durable KV, 24h TTL
if (seen) {
return { status: 'duplicate', id }; // caller returns 200, stops
}
await store.set(`evt:${id}`, '1', { ttl: 86400 });
return { status: 'new', id }; // continue the pipeline
};field checklist
- Return 2xx before slow work, so timeouts don’t trigger retries.
- Dedup on the provider’s delivery id, not payload contents.
- Store seen ids durably with a TTL past the retry window.
- Acknowledge duplicates with 200; never a 4xx/5xx.
- Design every downstream write to be safely repeatable.
common failure — Double-charged from a retried delivery
A payment pipeline ran the charge before responding. The endpoint took four seconds, the provider timed out at three and retried, and the customer was billed twice. Two fixes stack: acknowledge with 200 before the charge so timeouts stop retrying, and gate the charge on the delivery id so even a genuine duplicate is a no-op.
check your understanding
You recognise a delivery id you have already processed. Which HTTP status should the endpoint return so the provider stops retrying it?
next unit opens once this is passed
sandbox validation
The check above confirms you followed the unit. Marking the module COMPLETED takes more: build the automation in your own engine and submit the exported flow and its run evidence, signed, to your unique validation URL. See the module page for that spec.