Webhooks: APIs That Call You

API Types & Design — Webhooks: APIs That Call You
API Types & Design
Chapter 7 · Webhooks: APIs That Call You

📨 Webhooks: APIs That Call You

Every API style covered so far — REST, SOAP, RPC/gRPC — has your code as the one initiating a request. Webhooks flip that entirely: your server becomes the one receiving a request, sent by someone else's system the instant something happens on their end.

The Reverse Direction: Why Webhooks Exist

Imagine wanting to know the moment a payment succeeds. Without webhooks, the only option is polling — repeatedly asking "has it succeeded yet?" (GET /payments/123/status) every few seconds, wasting requests and adding delay between the real event and when you find out about it. A webhook lets the payment provider push a notification to you the instant it happens, instead.

Polling vs. Webhooks

Polling

Your code repeatedly asks "did anything change?" — wasteful when nothing has, and laggy since you only find out on your next poll, not the instant it happens.

Webhooks

The other system tells YOU, immediately, the moment something happens — no wasted requests, no polling delay.

How a Webhook Actually Works

  1. You register a URL ("endpoint") with the provider — e.g. "call this URL when a payment succeeds."
  2. Something happens on their end (a payment succeeds).
  3. Their server sends an HTTP POST request to your registered URL, with a body describing the event.
  4. Your server processes it and responds quickly with a 2xx status to acknowledge receipt.

A Realistic Payment-Succeeded Webhook Payload

POST /webhooks/payments HTTP/1.1 Content-Type: application/json X-Signature: t=1717000000,v1=5257a869e7... { "id": "evt_1Nx8k", "type": "payment.succeeded", "data": { "amount": 4999, "currency": "usd" } }

Signature Verification: Proving the Request Is Genuine

Your webhook endpoint is just a public URL — anyone on the internet could send a fake POST request to it pretending to be the real provider. To guard against that, providers sign each payload using a shared secret (agreed on when you registered the webhook) and include the signature in a header, like X-Signature above. Your server recomputes the same signature — typically an HMAC (Hash-based Message Authentication Code) over the raw request body, using that same shared secret — and only trusts the request if the computed signature matches the one in the header.

What HMAC Verification Confirms

That the payload genuinely came from someone holding the shared secret (presumably the real provider), and that it wasn't altered in transit — both of which a plain, unsigned POST can't guarantee.

What It Doesn't Do

It doesn't encrypt the payload (HTTPS/TLS already handles confidentiality in transit) — it only proves authenticity and integrity of the message itself.

Common Real-World Providers

Stripe sends webhooks for payment and subscription lifecycle events (succeeded, failed, refunded). GitHub sends webhooks for repository events (a push, a pull request opened, a comment posted) — the exact mechanism that powers a lot of CI/CD automation. Slack sends webhooks for message and interaction events, letting external apps react to things happening inside a workspace. All three follow the same shape this chapter describes: register a URL, get a signed POST the moment something happens.

Reliability: Retries and Idempotency

Providers generally can't guarantee your endpoint is always up, so most implement at-least-once delivery: if your endpoint doesn't respond with a 2xx quickly (or times out), they'll retry — which means your endpoint might genuinely receive the same event more than once. This is the same underlying problem Chapter 4's Idempotency-Key pattern solved for outgoing requests, just from the receiving side: each webhook payload includes a unique event id (like evt_1Nx8k above), and your handler should track which ids it's already processed, skipping any repeat instead of, say, charging a customer or sending a confirmation email a second time.

💻 Coding Challenges

Challenge 1: Polling vs Webhook

A food delivery app needs to know the instant a driver marks an order as delivered, to notify the customer immediately. Recommend polling or a webhook, and justify using this chapter's comparison.

Goal: Practice recognizing when the instant-notification property of webhooks genuinely matters.

→ Solution

Challenge 2: Verify a Webhook Signature

Describe, step by step, what a server should do upon receiving a webhook with an X-Signature header, to determine whether to trust the payload or reject it.

Goal: Practice explaining the HMAC verification process concretely, not just naming it.

→ Solution

Challenge 3: Handle a Duplicate Webhook Delivery

A payment.succeeded webhook with event id evt_1Nx8k arrives twice, a few seconds apart, because your server's first acknowledgement was slow. Describe how your handler should avoid sending the customer two confirmation emails.

Goal: Practice applying event-id-based deduplication to a concrete double-delivery scenario.

→ Solution

⚠️ Gotcha: Slow Webhook Handlers Cause Their Own Duplicate Deliveries

It's tempting to do the real work — sending a confirmation email, updating several database records, calling another service — directly inside the webhook handler, before responding. But if that work takes too long, the provider's request can time out waiting for your 2xx response, conclude the delivery failed, and retry — even though your server actually received and is still processing the original one. The fix mirrors the background-job pattern already covered in the Django, Rails, and Laravel courses: acknowledge receipt immediately with a fast 2xx, then hand the real work off to a background job or queue, processed asynchronously after the response has already gone out.

🎯 What's Next

The next chapter, Choosing the Right API Style, brings everything together — a decision framework comparing REST, SOAP, RPC/gRPC, GraphQL, WebSockets, and Webhooks by use case, so you can pick the right one deliberately instead of by habit.