Stop 09 · API DesignTopics · 11/12

Webhooks

Part of the Backend Developer Roadmap

In short

Asking a payment API 'paid yet?' every five seconds wastes everyone's time. With a webhook, the payment provider calls your server the moment the money arrives.

A way for your server to notify another system in real time by sending an HTTP request when an event happens — the inverse of a normal API call, where the 'server' becomes the caller.

The blob character sips chai on a sofa as a delivery person arrives at the door.

Think of it like this

The delivery guy rings the bell

You don't stand at the gate every two minutes waiting for a parcel. You sit with your chai, and the delivery person rings the bell when he arrives. A webhook is that bell: you share a URL, and the other service calls it when something happens.

Watch it happen

A payment succeeds

Payment app
Your server
Database

POST /webhooks/payments

1/5A payment succeeds. The provider sends the event to the URL you registered.

The big ideas

What you need to know

They call you

You expose an HTTPS endpoint. The other service POSTs JSON to it whenever an event happens.

Always verify the signature

Anyone can POST to your URL. Check the provider's HMAC signature before trusting the body.

Expect duplicates

Providers retry on failure, so one event can arrive twice. Store event ids and ignore repeats.

Answer fast

Return a 2xx within a few seconds and push slow work to a queue, or the provider marks the delivery failed.

See it in code

Walk through the code

ts
1import crypto from "node:crypto";2 3app.post("/webhooks/payments", express.raw({ type: "application/json" }), (req, res) => {4  const expected = crypto5    .createHmac("sha256", process.env.WEBHOOK_SECRET!)6    .update(req.body)7    .digest("hex");8  const got = String(req.header("x-signature") ?? "");9 10  if (got.length !== expected.length ||11      !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {12    return res.sendStatus(401);13  }14  queue.add("payment-event", JSON.parse(req.body.toString()));15  res.sendStatus(200);16});

1/4express.raw keeps the exact bytes. Signatures are computed on the raw body, not on parsed JSON.

The header name and format differ by provider, so check their docs. queue is any job queue, like BullMQ.

Save yourself the pain

Common mistakes

Oops

Processing a webhook without checking its signature.

Do this instead

Anyone who finds the URL could fake a 'payment succeeded'. Verify the signature and reject on mismatch.

Oops

Doing slow work like sending emails before replying.

Do this instead

The provider may time out and retry, causing duplicates. Acknowledge fast, then process in a background job.

Your mission

Catch a real webhook

~30 min

Quick check

Why should a webhook handler remember the event ids it has processed?

Go deeper

  1. 1Build an endpoint that receives a webhook from a third-party service (e.g. Stripe, GitHub).
  2. 2Verify webhook authenticity using a signature header, not just trusting the payload.
  3. 3Design your own webhook system that notifies external URLs when an event occurs in your app.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge