Why Webhooks Are Hard — The Contract Nobody Reads

Webhooks look like the easiest integration pattern in software: the provider POSTs you JSON when something happens. Then production arrives — events show up twice, out of order, late, or not at all; a deploy drops an hour of deliveries; a signature check fails silently — and the team discovers that webhooks are a distributed system whose other half they don't control. This series is the missing engineering discipline for both sides of that contract.

Webhooks are how the SaaS economy synchronizes — payments, deployments, messages, signatures, tickets all propagate between companies as HTTP callbacks. The pattern’s reputation for flakiness is really a reputation for unread contracts: the delivery semantics are published, loose, and almost universally designed against as if they were strict. This opening post states the real contract and maps the discipline the rest of the series builds.

The real contract

Strip any provider’s webhook documentation to its load-bearing clauses and the same contract emerges:

None of these clauses is hidden. Engineering against the contract-as-written, rather than the contract-as-wished, is essentially the whole discipline.

Why the contract can’t be stricter

The temptation is to blame providers; the physics says otherwise. Exactly-once delivery across independently operated systems over HTTP would require distributed transactions between strangers; ordered delivery would serialize throughput through per-recipient bottlenecks and let one slow consumer brake a fleet; guaranteed delivery would mean unbounded retention for endpoints that may never return. Every provider lands on the same trade — at-least-once, unordered, bounded retention — because it’s the one that scales across millions of endpoints they don’t operate. The consequence for receivers is liberating once accepted: the provider delivers possibilities; your system establishes truth. Idempotency (post 3), verification (post 4), durable intake (post 5), fetch-on-event (post 6), and reconciliation (post 8) are how.

The series map

The discipline splits by role. Receiving well — post 2 (delivery semantics and retries, the theory under the contract), post 3 (idempotent consumption and ordering recovery), post 4 (security: signatures, replay, SSRF), post 5 (the durable intake architecture: queue-behind-endpoint, DLQs, poison events). Choosing shapes — post 6 (thin vs fat events, fetch-on-event, events vs polling). Providing well — post 7 (building the delivery platform: fan-out, per-endpoint health, suspension policy — the view from the other side, which makes anyone a better receiver). Closing the loop — post 8 (reconciliation and durable sync: the backstop that turns webhooks from a hope into a guarantee). The connective theme is the one this post earns: webhooks are not notifications; they’re replicated state synchronization over an unreliable channel, and every pattern in the series is a standard distributed-systems answer wearing integration clothes.

The takeaway: the webhook contract — at-least-once, unordered, bounded-retention, latency-sensitive, point-in-time payloads — is loose by necessity, not negligence; exactly-once and ordering don’t survive contact with millions of independently operated endpoints. The failure pattern is designing against the contract you wish existed. The discipline is accepting that the provider delivers possibilities and the receiver establishes truth — through idempotency, verification, durable intake, fetch-on-event, and reconciliation — which is exactly the map this series walks.

Key takeaways

Further reading

Sources & References

the callback pattern itself