What is a Webhook?
An HTTP POST sent automatically when an event occurs. Reverse of polling: services push to you instead of you asking. The plumbing of modern integrations.
What is a webhook?
A webhook is an HTTP POST request that one service sends to another service automatically when something happens. The receiving service registers a URL with the sending service ahead of time; when the trigger event occurs, the sender POSTs a JSON (or form-encoded) payload to that URL. The receiver returns a 200 OK to acknowledge, and the sender moves on. No polling, no waiting — events delivered as they happen.
Webhooks are the plumbing of modern internet integrations. When Stripe charges a card, it POSTs a charge.succeeded event to your registered webhook URL. When GitHub merges a PR, it POSTs a pull_request.closed event. When Shopify ships an order, your app gets a order.fulfilled POST. The pattern is identical across thousands of SaaS products: I tell you a URL, you POST to me when stuff happens.
Webhooks vs polling: why webhooks won
Before webhooks, integration meant polling: your service repeatedly asks the API "anything new?" every few seconds. Polling has three problems:
- Latency. If you poll every 60 s, your average event detection lag is 30 s. Real-time features (chat, payments, alerts) can't tolerate this.
- Wasted load. 99% of polls return "nothing new". You're hammering the API for empty responses, paying compute and bandwidth for nothing.
- Rate limits. Most APIs cap requests per minute. Aggressive polling blows the budget; conservative polling makes the latency worse.
Webhooks invert the model. The provider knows when an event happens (it caused it), so it POSTs to you immediately — typically within hundreds of milliseconds. Zero polling, zero wasted requests, zero rate-limit waste. The provider gets simpler integration logic; the consumer gets near-realtime updates.
How a webhook delivery works (step by step)
A typical webhook lifecycle:
- You register a URL with the provider. Usually in a settings UI: "Send webhooks to
https://api.myapp.com/webhooks/stripe". The provider stores this URL. - An event happens on the provider's side. A user pays, a build completes, a row is updated.
- The provider builds a payload. Usually JSON:
{"event": "charge.succeeded", "data": {"id": "ch_123", "amount": 1500}}. - The provider POSTs the payload to your URL. Headers usually include the event type, a delivery ID for idempotency, and a signature header for verification.
- Your endpoint receives the POST and processes it. Verify the signature, parse the JSON, do whatever the event requires (update DB, send email, fan-out to other services).
- Your endpoint returns 200 OK quickly. Most providers consider non-2xx responses a delivery failure and retry with exponential backoff. If processing takes longer than a few seconds, accept the webhook (200) and queue the work for async processing.
Verifying webhook authenticity (don't skip this)
Anyone who knows your webhook URL can POST to it. Without verification, an attacker can fake events — pretend Stripe paid you, pretend GitHub merged a malicious PR, pretend a shipment arrived. Three common verification patterns:
- HMAC signature. The provider signs the payload with a shared secret using HMAC-SHA256, sends the signature in a header (
Stripe-Signature,X-Hub-Signature-256, etc.). Your endpoint recomputes the HMAC and rejects requests that don't match. Most modern providers (Stripe, GitHub, Shopify) use this. - Bearer token in header. Simpler but less robust: provider sends a static secret token in
Authorization: Bearer .... Anyone who captures one valid request can replay it; less common in modern systems. - IP allowlist. Only accept POSTs from documented provider IP ranges. Useful as defence-in-depth but breaks when providers rotate IPs.
HMAC signature is the right default. Combine with a short replay window (reject requests with timestamps older than a few minutes) to prevent replay attacks.
Idempotency: handle duplicate deliveries
Networks fail. Providers retry. Your endpoint will receive the same webhook twice — sometimes more. Naive integrations create duplicate database rows, send duplicate emails, charge customers twice. Idempotency prevents this.
Two common approaches:
- Idempotency key from the provider. Most providers include a delivery ID in headers (
Stripe-Webhook-Id,X-GitHub-Delivery) — globally unique per event. Store the ID after processing and reject duplicates. Simple, reliable. - Application-level dedup. Compute a hash of the event type + key fields (e.g. order ID + status) and treat repeated hashes within a TTL as duplicates. Useful when the provider's delivery ID isn't reliable.
Webhook reliability patterns
Acknowledge fast, process async
The webhook receiver should return 200 in under 1-2 seconds, even if the actual work takes longer. Push the payload onto a queue (SQS, Redis stream, Postgres LISTEN/NOTIFY) and process from a worker. Slow synchronous processing causes provider timeouts, retries, and duplicate deliveries.
Implement an outbox / inbox table
Persist incoming webhooks to an inbox table BEFORE doing any processing. If your worker crashes mid-processing, you can replay from the inbox. The provider's delivery ID becomes the inbox primary key for idempotency.
Monitor delivery success
Most providers expose a delivery log (Stripe → Webhooks → recent deliveries). Periodically check it. A spike in 5xx or 4xx responses on your endpoint usually means your inbox is broken before you'd otherwise notice.
Use webhook tunnels in development
Tools like ngrok, Smee, and Cloudflare Tunnel expose your localhost to the public internet so providers can POST to your dev environment. Essential for local debugging — webhooks can't reach localhost:3000 from Stripe's servers without one.
Common webhook gotchas
- Out-of-order delivery. Providers usually deliver events in order, but network retries can flip the order. Don't assume
order.createdarrives beforeorder.fulfilled— handle both possibilities. - Payload size limits. Many providers cap payloads at 4-8 MB. Large entities (e.g. a 50,000-row export) ship as a URL to fetch separately, not inline.
- HTTP/HTTPS strictness. Most providers require HTTPS; some reject self-signed certs. Use a real cert (Let's Encrypt is free).
- Body parsing before signature verification. Many HMAC schemes sign the raw body. If your framework parses JSON first, you may not have the raw bytes anymore. Use middleware that preserves the raw body for verification, then parses.
FAQ: Webhooks
Are webhooks the same as a callback URL?
Yes — "webhook", "HTTP callback", and "reverse API" all describe the same pattern. Different terms came from different communities (web developers, API providers, REST advocates) but the technical mechanism is identical.
What's the difference between webhooks and WebSockets?
Webhooks are unidirectional HTTP POSTs sent when events occur. WebSockets are persistent bidirectional TCP connections. Use webhooks for occasional events between services; use WebSockets for high-frequency bidirectional communication (chat, live cursors, real-time collaboration).
Can my receiver be hosted on serverless?
Yes — webhooks are a great fit for AWS Lambda, Cloudflare Workers, or Vercel functions. The brief acknowledgement model (return 200 fast, process async) maps well to serverless. Just size the function for fast cold starts; some providers timeout aggressively.
How do I test webhook handlers locally?
Use a tunnel: ngrok, Smee, Cloudflare Tunnel. The tunnel exposes your localhost over a public HTTPS URL that providers can POST to. Stripe, GitHub, and most modern providers also offer a CLI to forward events directly to localhost without a tunnel.
What happens if my endpoint is down when an event happens?
Most providers retry with exponential backoff (1 min, 5 min, 30 min, etc.) for 24-72 hours. After that, the event is dropped — and your data is permanently inconsistent. Monitor delivery failures and have an idempotent replay mechanism for outages longer than the retry window.
Should I use webhooks or polling for my use case?
Webhooks for: real-time events, low-frequency triggers, payment notifications, anything where latency matters. Polling for: bulk data sync, situations where the provider doesn't offer webhooks, or when you can't run a public HTTPS endpoint. In practice, modern integrations use both: webhooks for immediate alerts, periodic polling as a safety net to catch missed deliveries.
How LoadFocus uses webhooks
LoadFocus exposes webhooks for test completion and alert events: when your scheduled load test finishes, we POST the results URL to your registered endpoint so your CI pipeline can react (post to Slack, gate a deploy, update a dashboard). When an API monitor fires an alert, the same payload pattern delivers the incident to PagerDuty, Opsgenie, or your custom service. Configure webhooks in your account settings; HMAC signatures are included by default.
Related LoadFocus Tools
Put this concept into practice with LoadFocus — the same platform that powers everything you just read about.