You don’t control the sender
Webhooks arrive on the sender’s schedule, retry on the sender’s policy, and give up when the sender decides. Every design assumption you would make for your own traffic is theirs to make instead.
1. Acknowledge fast, process later
Accept the payload, store it, return success immediately, and do the work asynchronously. Processing inline means a slow database turns into a timeout, which the sender reads as failure and retries — the same queueing argument as slow model calls.
2. Expect duplicates
Retries mean the same event arrives more than once, and a handler that isn’t idempotent will double-charge, double-email, or double-count. Deduplicate on the event identifier before doing anything with consequences.
Assume every webhook arrives twice. The ones that only arrive once cost you nothing.
3. Verify it came from who it claims
A public endpoint accepting state changes without signature verification is an open door. This belongs with the rest of protecting a public endpoint, and it is the control most often skipped in a hurry.
4. Order is not guaranteed
Events can arrive out of sequence — an update before the create, a cancellation before the booking. Handle each by checking current state rather than assuming what preceded it.
Keep the raw payload
Store what was received verbatim before you interpret it. When a customer disputes what a third party sent, the original is the only answer — and replaying stored payloads is how you recover from a handler bug without asking the sender to resend.