Webhooks are how services tell you things happened — a payment succeeded, a form was submitted, a build finished. Receiving them correctly is a specific, well-defined problem with a few non-obvious rules.

1. Verify before you trust

Your endpoint is public. Anyone can POST to it claiming to be Stripe. Every serious provider signs their requests; verify that signature before you read a single field.

Verification usually needs the raw body, not the parsed JSON — a detail that catches people out, because many frameworks parse the body before your code runs.

webhook-endpoint.txt
Build a webhook receiver for <provider> at <path>. Stack: <stack>.

- Verify the signature against the raw request body, before parsing.
  Show me how to keep the raw body available in <framework>.
- Store the event immediately, then return 200. Do the actual work
  afterwards, in the background.
- Idempotent: the same event ID delivered twice must have no second
  effect. Providers retry, and duplicates are normal.
- Log every event received, including ones that fail verification.
- Return 200 for events I don't handle — a 404 makes the provider
  retry forever.

2. Acknowledge fast, work later

Providers time out in seconds, and a timeout means a retry. If you do the work before responding, a slow job produces duplicate deliveries and eventually gets your endpoint disabled.

Store the event, return 200, process from your own queue. This also means a bug in your processing doesn't cost you the event.

3. Idempotency is mandatory

Not defensive coding — duplicates are guaranteed, by design. Every provider retries on any failure, including your successful-but-slow response.

Store processed event IDs and check before acting. Without this you double-charge, double-email and double-grant.

4. Handle out-of-order arrival

Events do not arrive in the order they happened. "Subscription cancelled" can land before "subscription created". Where order matters, use the timestamps in the payload rather than arrival order, and ignore events older than your current state.

Keep a page listing recent webhook events and their processing status. The first time something goes wrong, it's the difference between minutes and hours.