A webhook is an HTTP request one system sends to another when something happens: a payment succeeds (Stripe), code is pushed (GitHub), a form is submitted, an order ships. Instead of polling an API every minute, your server is notified instantly.

How it works

  1. You register a URL with the provider, e.g. https://example.com/webhooks/stripe, and choose events.
  2. When an event occurs, the provider sends a POST with a JSON payload to that URL.
  3. Your endpoint verifies it, acknowledges with a 2xx status, and processes the event.
  4. If your endpoint fails or times out, the provider retries — often for hours or days with backoff.

Rule 1: verify the signature

Anyone can POST to a public URL. Providers sign webhooks so you can confirm they’re genuine — usually an HMAC of the raw request body with a shared secret, sent in a header (Stripe-Signature, X-Hub-Signature-256, etc.).

Example (Node.js, GitHub-style HMAC SHA-256):

import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post('/webhooks/github', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.get('X-Hub-Signature-256') || '';
  const expected = 'sha256=' + crypto.createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body)          // the RAW body, not re-serialised JSON
    .digest('hex');
  const ok = signature.length === expected.length &&
             crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  queue.add('github-event', event);   // process later
  res.sendStatus(204);
});

Key details:

  • Compute the HMAC over the raw bytes — parsing and re-stringifying JSON changes whitespace and breaks the signature.
  • Use a constant-time comparison (timingSafeEqual).
  • Check the timestamp if the provider includes one, and reject old requests to prevent replay attacks.
  • Use the provider’s SDK helper when available (e.g. Stripe’s constructEvent).

Rule 2: respond fast, process asynchronously

Providers expect a response within a few seconds. Don’t send emails, generate PDFs or call slow APIs inside the request. Instead:

  1. Verify the signature.
  2. Store the event or push it to a queue (Redis/BullMQ, Celery, Laravel queues, a database table).
  3. Return 200/204 immediately.
  4. A background worker processes the event.

This also keeps your endpoint healthy during traffic spikes. See background jobs and queues.

Rule 3: be idempotent

Retries mean you will receive some events more than once — and sometimes out of order. Make processing safe to repeat:

  • Store each event’s unique ID and skip ones you’ve already handled.
  • Use upserts and state checks (“only mark paid if currently pending”).
  • Don’t rely on delivery order; fetch the current state from the provider’s API when order matters.

Other good practices

  • Return non-2xx for real failures so the provider retries; return 2xx for events you deliberately ignore.
  • Log event IDs, types and outcomes; providers’ dashboards show delivery attempts too.
  • Use HTTPS with a valid certificate — many providers require it.
  • Restrict by IP if the provider publishes source ranges (GitHub, Stripe do), as an extra layer — not a replacement for signatures.
  • Rotate secrets periodically; support two secrets during rotation.
  • Rate-limit the endpoint to protect against floods.

Testing locally

Your laptop isn’t reachable from the internet. Options:

  • Provider CLIs that forward events (stripe listen --forward-to localhost:3000/webhooks/stripe).
  • Tunnels: Cloudflare Tunnel, or your own VPS with an SSH reverse tunnel (ssh -R) or frp. See ngrok alternatives.
  • Webhook inspection services to see raw payloads.

Hosting a webhook receiver

Webhook endpoints must be always online — downtime means retries pile up or events are dropped after the retry window. A small VPS with a static IP and HTTPS is ideal; tools like n8n can receive webhooks and run automations without writing code.

Related: REST vs GraphQL and API security best practices.

  • Webhooks
  • APIs
  • Web Development