Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A webhook is an event-driven HTTP callback. When something happens in a provider or source application, that application sends an HTTP request—usually a POST containing JSON—to a URL owned by your application. Instead of repeatedly asking an API whether anything changed, your system receives the notification when the event occurs.

A production-quality webhook integration must do more than accept JSON. It should authenticate the sender, validate the exact request body, record a delivery ID, acknowledge quickly with a 2XX response, and process the event asynchronously and idempotently. The provider’s documentation remains authoritative for event names, headers, signatures, payload limits, retries and timeouts.

How a webhook works

There are two applications:

  • Provider (sender): detects an event such as a payment succeeding, a repository changing, or a capture job finishing.
  • Consumer (receiver): exposes an HTTPS endpoint and performs work based on the notification.
  1. You deploy a publicly reachable HTTPS endpoint, for example https://example.com/webhooks/provider.
  2. You register that URL and the event types you want in the provider’s dashboard or API.
  3. The provider detects a subscribed event and sends an HTTP POST.
  4. The request includes a body (commonly JSON) and metadata headers such as an event type, a delivery ID and a signature.
  5. Your endpoint authenticates and validates the request, stores the delivery ID, places the event on a queue, and returns a fast 2XX response.
  6. A worker performs the slow or irreversible operation and records its result.

CloudEvents’ HTTP binding requires POST and a Content-Type header carrying the notification payload. The Standard Webhooks specification recommends JSON in the body but does not define one universal event schema. Consequently, two webhook providers can use completely different envelopes even when they report similar events.

A representative request

POST /webhooks/orders HTTP/1.1
Host: example.com
Content-Type: application/json
X-Event-Type: order.created
X-Delivery-Id: 8f2e...
X-Signature: sha256=...

{"id":"ord_123","customer_id":"cus_456","total":4200}

The header names above are illustrative. Use the exact names and signing algorithm documented by your provider.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Webhook versus API and polling

Webhook and API are complementary

An API is an interface for making requests and receiving responses. A webhook is a delivery mechanism that lets an API provider initiate a request to your application after an event. You might use an API to create an order, then receive a webhook when payment for that order is completed.

Webhook versus polling

Concern Webhook Polling
Notification timing Usually near the event, subject to provider delivery and network delay Only when the next poll runs
Request volume Requests generally occur for events Repeated requests occur even when nothing changed
Receiver requirements Publicly reachable endpoint, authentication, retry and duplicate handling Client needs API credentials and a schedule; no inbound endpoint is required
Failure model Provider retries may create duplicate deliveries; you must acknowledge and deduplicate Client retries failed polls and must track cursors or last-seen state
Implementation More operational work at the receiver, but efficient for event-driven updates Often simpler initially, but can add latency and unnecessary API traffic

Polling can be preferable when inbound traffic is impossible, the provider has no webhook facility, or periodic reconciliation is more important than immediate notification. Many robust systems use both: webhooks for prompt updates and scheduled API polling to repair missed events.

Build a secure webhook endpoint

1. Use HTTPS and a dedicated route

Serve the endpoint over HTTPS and keep it separate from browser-facing form routes. Do not put secrets in the URL, query string or path. Restrict accepted methods to POST, enforce a sensible body-size limit, and reject unsupported content types.

2. Verify the signature over the raw body

Treat every request as untrusted until authenticated. Configure a high-entropy secret, store it in a secret manager or environment variable, and calculate the provider’s specified signature over the exact bytes received. Verify the signature before parsing JSON or performing any action. Use constant-time comparison to avoid timing leaks.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

GitHub, for example, documents an HMAC SHA-256 value in X-Hub-Signature-256. That algorithm and header are GitHub-specific; another provider may use a different scheme, timestamp format or header.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
const crypto = require('node:crypto');

function validSignature(rawBody, received, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(received || '', 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Capture the raw bytes before a JSON parser transforms whitespace, character encoding or key ordering. Follow the provider’s replay-window or timestamp rules when they are specified.

3. Deduplicate before side effects

Retries and network behavior can deliver the same event more than once. Persist the provider’s delivery or event ID in durable storage with a uniqueness constraint before sending email, charging a card, provisioning access or making another irreversible change. If the ID already exists, return success without repeating the work.

The Standard Webhooks specification describes the unique event identifier as remaining the same across retries. Do not assume every provider uses the same field name; map its documented identifier to your internal idempotency key.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Acknowledge quickly, then queue

Validate, persist and enqueue the event, then return a 2XX response as soon as possible. Long-running work inside the request handler increases timeout and retry risk. GitHub recommends responding within 10 seconds; other providers can impose different limits. A queue or background worker lets you retry application work independently of provider delivery.

Runnable receiver examples

Node.js with Express

This example keeps the raw body for HMAC validation and uses an in-memory set only for demonstration. Replace it with a transactional database table and a real queue in production.

const express = require('express');
const crypto = require('node:crypto');
const app = express();
const secret = process.env.WEBHOOK_SECRET;
const seen = new Set();

app.post('/webhooks/orders', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.get('X-Signature');
  const expected = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(req.body).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || '');
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.sendStatus(401);
  }

  let event;
  try { event = JSON.parse(req.body.toString('utf8')); }
  catch { return res.sendStatus(400); }

  const deliveryId = req.get('X-Delivery-Id');
  if (!deliveryId) return res.sendStatus(400);
  if (seen.has(deliveryId)) return res.sendStatus(204);
  seen.add(deliveryId);

  // Enqueue event here; do not perform slow work in this handler.
  console.log(event);
  return res.sendStatus(204);
});

app.listen(3000, () => console.log('Listening on :3000'));

Python with Flask

import hashlib
import hmac
import json
import os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["WEBHOOK_SECRET"].encode()
seen = set()  # Use a durable unique table in production.

@app.post("/webhooks/orders")
def orders():
    raw = request.get_data(cache=False)
    received = request.headers.get("X-Signature", "")
    expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, received):
        abort(401)
    delivery_id = request.headers.get("X-Delivery-Id")
    if not delivery_id:
        abort(400)
    if delivery_id in seen:
        return ("", 204)
    try:
        event = json.loads(raw)
    except ValueError:
        abort(400)
    seen.add(delivery_id)
    # Enqueue event here; return before slow business work.
    print(event)
    return ("", 204)

Send a test delivery with cURL

For a real signature, calculate the provider-specific value over the exact body. This command demonstrates the HTTP shape only:

curl -i -X POST https://example.com/webhooks/orders 
  -H 'Content-Type: application/json' 
  -H 'X-Event-Type: order.created' 
  -H 'X-Delivery-Id: test-001' 
  -H 'X-Signature: sha256=REPLACE_WITH_VALID_SIGNATURE' 
  --data '{"id":"ord_123","total":4200}'

Retries, replay and failure handling

A provider can retry when your endpoint times out, returns an error, or cannot be reached. A retry schedule, maximum attempts, timeout and manual redelivery controls are provider-specific. Record each delivery attempt and correlate it with the stable event or delivery ID.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Return a 2XX only after authentication, schema validation and durable enqueue or persistence succeed.
  • Return a 4XX for an invalid signature, malformed body or unsupported event that should not be retried.
  • Return a 5XX or allow a timeout when a temporary dependency failure means the provider should retry.
  • Keep a dead-letter or failed-event queue, with controlled replay after fixing the cause.
  • Make workers idempotent as well as the HTTP endpoint; a queued message can also be delivered twice.

Keep payloads and logs free of unnecessary personal or secret data. Redact authorization headers and signature secrets, and apply retention limits to stored event bodies.

Provider differences you must check

There is no universal webhook contract. Before implementing, document:

  • Event names, versioning and whether events are snapshots or partial updates.
  • Envelope fields, content type, character encoding and maximum body size.
  • Signature algorithm, signed components, timestamp tolerance and key rotation process.
  • Delivery and event identifiers, retry schedule, timeout and redelivery controls.
  • Whether ordering is guaranteed, and how to fetch the authoritative object through the API.
  • Test-event behavior, IP ranges, mutual TLS or private networking options, if offered.

For example, GitHub documents a 25 MB payload cap and its own event and signature headers. That limit and those names must not be generalized to other services.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Testing and operations checklist

  1. Test valid events, invalid signatures, altered bodies, missing headers and malformed JSON.
  2. Send the same delivery twice and verify that business effects happen once.
  3. Force a queue or database outage and confirm the provider receives a retryable response.
  4. Exercise slow downstream work and verify the endpoint still acknowledges within the provider’s timeout.
  5. Test old timestamps and replayed signatures where the provider supports replay protection.
  6. Monitor acceptance rate, authentication failures, processing latency, queue age, retries and dead-letter volume.
  7. Provide an authenticated operator path to inspect and replay a failed delivery without disabling signature checks.

Or skip the browser setup

Webhooks are also useful when an asynchronous service tells your system that a job has finished. ScreenshotNeo can submit capture jobs with signed webhooks, alongside its synchronous screenshot API. It is a website screenshot API and MCP server for developers; its cleanup steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups and chat widgets before capture, with each step configurable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and each response reports the result through X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

One synchronous call looks like this (see the ScreenshotNeo documentation for parameters and webhook job details):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

And Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to try it.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common errors and fixes

401 or 403 responses

Usually the signature was calculated from a parsed body, the wrong secret or header was used, or a timestamp has expired. Capture raw bytes, check the provider’s exact signing recipe and rotate secrets through a controlled overlap period.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Repeated deliveries

Do not treat repetition as proof of provider failure. Persist the documented event or delivery ID with a uniqueness constraint and make the worker idempotent.

Timeouts and request storms

Move network calls and heavy computation to a queue, return only after durable enqueue, and use bounded worker concurrency. A fast 2XX without persistence can lose events, so acknowledgement must follow a durable handoff.

Valid events rejected as malformed

Check content-type handling, compression, character encoding and body-size limits. Validate against the provider’s current schema and tolerate documented additive fields.

Events arrive out of order

Do not assume ordering unless the provider guarantees it. Use event timestamps or sequence numbers where available, fetch current state from the API, and make updates conditional on version or revision.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can a webhook response contain useful data?

Yes, but most providers primarily need a status code confirming receipt. Put business results in your own API or follow-up event rather than making the sender wait for slow work.

Should webhook endpoints require user login?

Usually not interactive login. Authenticate the machine-to-machine request with the provider’s signature, secret, certificate or network control, and protect operational dashboards separately.

Can I run a webhook receiver on localhost?

Not directly from a public provider. Use a secure tunneling or staging service for development, then deploy the endpoint on reachable HTTPS infrastructure and restrict test credentials.

What happens if the provider has no retry mechanism?

Persist events, alert on gaps, and schedule reconciliation against the provider’s API. A webhook should not be your only source of truth when the service offers no redelivery.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.