What is a Webhook? Definition, Examples & How to Set One Up

A webhook is an automated HTTP request a service sends when an event happens. How webhooks work, examples, and how to set one up in Node.js.

A webhook is an automated HTTP request that one system sends to a URL you control whenever a specific event happens. Instead of your application constantly asking another service "has anything changed yet?", the service simply pushes the information to you the instant it does.

For technical folks, it's easiest to think of webhooks as "reverse APIs" or "push APIs": one server makes an API request to another server. (For a deeper comparison, see webhooks vs API.)

If you prefer a plain-language version: imagine a friend who bakes cakes. Rather than you checking the kitchen every five minutes, your friend texts you the moment a cake is ready. A webhook is that text message between web services.

How do webhooks work?

Every webhook integration follows the same four steps:

  1. You expose a URL that accepts POST requests — an ordinary route in your app, like https://example.com/webhooks/stripe.
  2. You register that URL with the provider and choose which events you care about (Stripe → Developers → Webhooks, GitHub → Settings → Webhooks, and so on).
  3. The provider POSTs to your URL each time one of those events happens, sending a JSON payload plus headers describing the event and proving it's authentic.
  4. Your handler acknowledges with a 2xx status. If you don't — or you're too slow — most providers treat it as a failure and retry later.

Here's what actually arrives on the wire when a payment succeeds:

POST /webhooks/stripe HTTP/1.1
Host: example.com
Content-Type: application/json
Stripe-Signature: t=1754640000,v1=5257a869e7ecebeda32affa62cdca3fa...

{
  "id": "evt_1PgkT2xQ",
  "type": "checkout.session.completed",
  "created": 1754640000,
  "data": {
    "object": {
      "id": "cs_test_a1b2c3",
      "amount_total": 4900,
      "currency": "usd",
      "customer_email": "[email protected]"
    }
  }
}

That's the whole mechanism — a POST, a JSON body, and a signature header. Everything else is a detail of the specific provider. Curious which providers generate the most real-world webhook traffic, and what those payloads typically look like? We publish live figures from our own ingesters on the State of Webhooks.

What are examples of webhooks?

The most common real-world webhook examples:

  1. Payments — when a payment is made, a webhook is sent to the merchant's server so it can process the order. See receiving Stripe webhooks on localhost.
  2. Software testing / CI/CD — when code is pushed to GitHub, a webhook notifies a CI system to run tests and deploy. See the GitHub + Jenkins guide.
  3. Connecting services together — when you want to glue two third-party tools together, webhooks are the bridge. See Google Home + IFTTT + Node-RED.
  4. Notifications — posting a message to a Discord or Slack channel when something happens, like a TradingView alert.

Anatomy of a webhook request

Every webhook is an ordinary HTTP request, so it has three parts you'll work with:

  • The method and URL — almost always a POST to the endpoint URL you registered with the provider.
  • Headers — metadata about the delivery. The important ones are the Content-Type (usually application/json), an event-type header (e.g. GitHub's X-GitHub-Event), and a signature header the provider uses to prove the request is authentic (e.g. Stripe-Signature, X-Hub-Signature-256).
  • The body (payload) — the JSON describing what happened: the event type, an ID, a timestamp, and the data object.

Knowing the exact shape of these three parts is the whole job when you integrate a new provider — which is why it helps to capture a real request and inspect it — a free webhook.site alternative — before writing any handler code.

Webhooks vs APIs vs polling

ApproachDirectionWhen data movesBest for
REST APIYou → provider (pull)When you askOn-demand reads and writes
PollingYou → provider (pull, on a timer)Every N seconds, whether or not anything changedSimple, but wasteful and slow
WebhookProvider → you (push)The instant an event happensReal-time reactions to events

Webhooks replace polling: instead of asking "has anything changed?" on a loop, the provider tells you the moment it does. See the full breakdown in webhooks vs API, and how they differ from a persistent connection in webhook vs WebSocket.

How to receive a webhook in an application

A webhook is, at its core, just a POST request to a URL on your server. Instead of a person visiting your site or a CLI calling your API, it's another website POSTing data to you. So to receive one, you need a server listening for POST requests on a specific path.

Here it is in Node.js with Express:

npm init -y
npm install express

Create index.js:

const express = require('express')
const app = express()
app.use(express.json())

const port = 3000

app.post('/webhooks', (req, res) => {
  console.log('Received webhook:', req.body)
  res.sendStatus(200) // always acknowledge quickly
})

app.listen(port, () => {
  console.log(`Listening for webhooks on port ${port}`)
})

Start it:

node index.js

And send a test webhook with curl:

curl -X POST --data '{"important":"data"}' \
  -H 'Content-Type: application/json' \
  http://localhost:3000/webhooks

That's about as complicated as it gets — you can now receive webhooks from any service and process them.

How to set up a webhook (and receive it on localhost)

Setting up a webhook is two steps:

  1. Expose a public URL that listens for POST requests (the handler above).
  2. Register that URL in the sending service's webhook settings (e.g. Stripe → Developers → Webhooks, or GitHub → Settings → Webhooks).

The tricky part is step 1 during development: providers can only call public URLs, but your handler runs on localhost. That's what Webhook Relay solves — inspect the incoming payload in your browser, then forward it to your machine with the agent:

relay forward --bucket my-app http://localhost:3000/webhooks

No public IP, no firewall changes, and the URL stays the same so you configure the provider once. See how to test webhooks for the full workflow.

Are webhooks secure?

A webhook endpoint is a public URL — anyone who discovers it can POST to it. On its own that's a real risk: a forged payment.succeeded event could have your app ship an order nobody paid for.

Webhooks become secure when you verify that each request genuinely came from the provider. Almost all of them sign the payload with an HMAC computed over the raw request body using a shared secret, delivered in a header such as Stripe-Signature or X-Hub-Signature-256. Your handler recomputes that HMAC and compares it before trusting anything.

Four rules cover most of it:

  • Verify against the raw bytes. Parsing JSON and re-serializing it changes the body, and every signature check will fail.
  • Compare in constant time (crypto.timingSafeEqual in Node) so you don't leak the secret through timing.
  • Check the timestamp where the provider sends one, so an old captured request can't be replayed.
  • Always use HTTPS, and treat the secret like any other credential.

You can test a signature against your secret right in the browser with the webhook signature verifier, including provider-specific pages for Stripe, GitHub, Shopify, Slack and Twilio.

Webhook best practices

  • Always respond 2xx quickly. If you return an error or respond slowly, the sender will retry. Do heavy work asynchronously and acknowledge first. Only return 5xx when you genuinely can't process the event (e.g. the database is down) and want a retry.
  • Verify the signature. Most providers sign webhooks with an HMAC so you can confirm authenticity. See how to verify a webhook signature and the free HMAC verifier.
  • Don't rely on ordering. Webhooks can arrive out of order. If order matters, use a queue or sort by a timestamp in the payload.
  • Make handlers idempotent. The same event can be delivered more than once; de-duplicate on an event ID so reprocessing is safe.
  • Keep payloads thin. If you're sending webhooks, send identifiers and let the receiver fetch full data via your API. This avoids stale data and keeps payloads small.

For the full checklist, see webhook security best practices, webhook authentication methods and retries & idempotency.

How to test and debug webhooks

You can't see a webhook until something sends one, so the fastest way to learn a provider's payload is to capture a real request:

  1. Capture & inspect — point the provider at a free webhook tester URL and read the exact headers and body.
  2. Verify the signature — confirm authenticity with the HMAC verifier and the verify a webhook signature guide.
  3. Forward to localhost — drive your local handler with real events using the relay agent. See how to test webhooks and how to debug webhooks.

Webhooks by provider

Most integrations follow the same pattern with provider-specific signatures and event shapes. These guides cover the exact details for popular services — capture and inspect the payload, then receive it on localhost:

Looking for a no-code option? Every provider also has a dedicated tester — for example the Stripe webhook tester and GitHub webhook tester.

Routing and transforming webhooks

Once you receive a webhook you often want to send it somewhere else, or reshape it first. Webhook Relay can transform payloads and fan a single event out to multiple destinations:

Setting up the destination first? Here's how to get a Slack webhook URL, a Discord webhook URL, a Microsoft Teams webhook, or how to send a webhook from Google Forms.

Next steps