Lesson 11 of 17
24. APIs and Idempotency

24.10 How do webhooks work?

3 min read+10 XP

At the machine: You don't keep phoning the bank to ask whether your payment cleared. The bank sends you an SMS when it does. And you'd ignore an SMS that didn't come from your bank's number.

In the system: A webhook is the server calling you: when something happens on the provider's side ("payment succeeded", "refund issued"), it sends an HTTP POST to a URL you registered. It's the server-to-server version of push (Chapter 21).

the payment provider posts a signed event to the bookstore's webhook endpoint and retries until it gets a 2xx.

Figure: verify it, de-duplicate it, acknowledge it quickly, then process it.

For the bookstore: some payments (UPI collect requests, bank transfers) finish minutes later. Instead of polling, the bookstore registers POST /webhooks/payments, and the provider calls it with payment.succeeded for order 881.

Receiving webhooks safely:

  1. Verify the signature. The provider signs each delivery with a shared secret, typically an HMAC-SHA256 of a timestamp plus the body (Stripe sends it in a Stripe-Signature header). Recompute and compare in constant time; otherwise anyone could post "payment succeeded" to your endpoint. In the chapter's code, a forged event is rejected with 400.
  2. Reject old timestamps (say, older than 5 minutes) so a captured request can't be replayed later.
  3. De-duplicate by event ID. Providers deliver at least once and retry on failure, so the same event can arrive more than once. In the chapter's code, a retried delivery returns 200 but marks order 881 paid only once.
  4. Respond 2xx quickly, then process from a queue (Chapter 22). Slow handlers cause timeouts, which cause retries, which cause duplicates.
  5. Don't rely on order. "Refunded" might arrive before "succeeded". Use timestamps or fetch the current state from the provider's API.
  6. Have a fallback. If your endpoint is down for hours, providers retry with backoff for a limited time (Stripe retries for up to three days), but also reconcile periodically by listing recent payments.

Sending webhooks (if you offer them): sign every delivery, retry with backoff, let customers see delivery logs and replay events, and disable endpoints that keep failing.

Memory code: "Check it's really the bank, ignore the second SMS, and say 'got it' fast."