What is a webhook? Meaning, how it works and a real example

A webhook is an HTTP request, usually a POST with JSON, that an app sends to a URL you choose the moment an event happens, so you get the data without polling. GitHub expects a reply within 10 seconds, Slack within 3. Watch one arrive on a free 48-hour URL.

A webhook is an HTTP request, usually a POST with a JSON body, that one app sends to a URL you give it the moment an event happens, such as a payment succeeding or an issue being opened. Put plainly, the app calls you, instead of your code asking it every minute; GitHub's docs put it as receiving data as it happens, as opposed to polling an API. Below we'll walk through the steps of one delivery, look at a real GitHub request, cover what people use webhooks for and how 5 well-known providers sign and retry them, and show how to watch one arrive on a free URL in about 30 seconds.

How a webhook works, step by step

  1. You give the provider a URL (called the webhook URL, endpoint or callback URL) and pick the events you care about, once.
  2. An event happens: a payment succeeds, code is pushed, an order is placed.
  3. The provider sends an HTTP POST to your URL; the body describes the event (JSON for GitHub, Stripe, Shopify and Slack, form fields for Twilio) and the headers carry the event name, a delivery ID and a signature.
  4. Your endpoint checks the signature and answers with a 2xx status fast: GitHub allows 10 seconds, Shopify 5, Slack 3.
  5. If the answer is late or an error, the provider retries on its own schedule, or not at all: GitHub doesn't retry automatically.
  6. Your code does the slow work after answering and skips events it has already handled, because the same event can arrive twice.

That's the whole mechanism, an ordinary HTTP request going the other way.

What a real webhook request looks like

Here is GitHub's own example delivery for an issues event. It starts with the POST line and the path on your server, followed by the headers: X-GitHub-Event names the event (issues), X-GitHub-Delivery is a unique ID per delivery that you can use to skip repeats, X-Hub-Signature-256 is an HMAC SHA-256 of the body made with your webhook secret (the value is shortened on this page), and User-Agent starts with GitHub-Hookshot/.

Below the headers, the JSON body says what happened: action is opened, issue number is 1347 in octocat/Hello-World, sent by octocat. GitHub trims the body with ... in its own docs, and caps payloads at 25 MB. For real payloads from Stripe, Shopify, Slack and Twilio, see our webhook examples guide.

HTTP headers (GitHub's example)

POST /payload HTTP/1.1
X-GitHub-Delivery: 72d3162e-cc78-11e3-81ab-4c9367dc0958
X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff2693893...
User-Agent: GitHub-Hookshot/044aadd
Content-Type: application/json
X-GitHub-Event: issues
X-GitHub-Hook-ID: 292430182

JSON body (trimmed by GitHub)

{
  "action": "opened",
  "issue": {
    "number": 1347,
    ...
  },
  "repository": {
    "id": 1296269,
    "full_name": "octocat/Hello-World",
    "owner": {
      "login": "octocat",
      "id": 1,
      ...
    },
    ...
  },
  "sender": {
    "login": "octocat",
    "id": 1,
    ...
  }
}

What webhooks are used for

Teams use webhooks whenever an action in one service needs to kick off work in another.

  • Getting paid: Stripe sends payment_intent.succeeded and checkout.session.completed so you can fulfill an order.
  • Shipping code: GitHub push and pull_request events trigger CI in Jenkins or CircleCI, deploy to production or update Jira.
  • Running a store: Shopify sends orders/create when an order is placed and products/update when a product changes.
  • Chat alerts: post to Slack or Discord when a pull request gets a review.
  • SMS: Twilio calls your URL when your number receives a text.

The common thread is that something happens in someone else's system and your code needs to know right away, without asking over and over (see webhooks vs APIs for when calling the API is the better choice).

How Stripe, GitHub, Shopify, Slack and Discord send webhooks

The big providers follow the same pattern, but they set different rules for the signature header, how fast you must answer and what happens when you don't.

Watch out for two traps: GitHub won't retry a failed delivery on its own (you have to redeliver by hand or through the REST API within 3 days), and Shopify removes the webhook subscription altogether if failures persist through 8 tries in 4 hours. We cover each provider's schedule in the webhook retries guide.

ProviderExample eventSignature headerAnswer withinIf you miss it
Stripepayment_intent.succeededStripe-SignatureQuickly, before slow workRetries for up to 3 days in live mode
GitHubissues (opened)X-Hub-Signature-25610 secondsNo automatic retry; redeliver within 3 days
Shopifyorders/createX-Shopify-Hmac-Sha2565 secondsUp to 8 retries in 4 hours, then the subscription is removed
Slack (Events API)reaction_addedX-Slack-Signature3 secondsUp to 3 retries: at once, after 1 minute, after 5 minutes
Discord (webhook events)APPLICATION_AUTHORIZEDX-Signature-Ed255193 seconds, with a 204Retries with backoff for up to 10 minutes

See a webhook arrive in 30 seconds

You can watch a real delivery without writing a server or deploying anything.

  1. Create a free bin in your browser at view.webhooks.page or with the first curl command below (no signup).
  2. Play the provider yourself with the second command, which sends a POST with a small GitHub-style issues event and an X-GitHub-Event header.
  3. Open the view_url in your browser and watch the request appear live with its method, path, headers and JSON body.

The bin answers every request with HTTP 200 and a small JSON body, so GitHub, Stripe and Shopify count a delivery to it as successful, and you can paste the same ingest_url into their webhook settings to see their real requests. Free bins last 48 hours, take 100 requests a day and bodies up to 100KB, and the viewer shows the latest 10 requests. Claim the bin with a free Google sign-in before it expires to keep the same URL with 30 days of rolling history.

# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins

# 2. Be the sender: post an event to the ingest_url
curl -X POST https://<bin>.webhooks.page/payload \
  -H "Content-Type: application/json" \
  -H "X-GitHub-Event: issues" \
  -d '{"action":"opened","issue":{"number":1347}}'

What your webhook endpoint has to get right

When you move from a test bin to your own endpoint, a few details matter right away.

  • Use HTTPS: Stripe requires it in live mode and only accepts TLS 1.2 or 1.3.
  • Verify the signature header against the raw request body before trusting anything; Stripe's Stripe-Signature also signs a timestamp t, and Stripe's libraries reject anything older than 5 minutes by default, which stops replays.
  • Answer with a 2xx status fast, before running slow database or network work.
  • Expect duplicates and events arriving out of order: Stripe doesn't guarantee order, and GitHub reuses the X-GitHub-Delivery GUID when you redeliver, so store the event or delivery ID and skip repeats.
  • When money or access depends on it, fetch the latest state from the provider's API instead of trusting an old payload.

See the webhook security guide for verification details.

FAQ

What is a webhook in simple terms?

A webhook is an app calling a URL you gave it when something happens, instead of your code asking the app over and over. For example, Stripe sends a request to your server the moment a payment succeeds.

What is the difference between a webhook and an API?

With an API, your code makes the request when it wants data; with a webhook, the provider makes the request to your URL when an event happens. Most apps use both together, and our guide on webhooks vs APIs covers when to pick each one.

Is a webhook a GET or a POST request?

A webhook is almost always an HTTP POST request. GitHub, Stripe, Shopify and Slack send a POST with a JSON body, while Twilio sends form-encoded fields (application/x-www-form-urlencoded).

What happens if my webhook endpoint is down?

It depends on the provider. Stripe retries failed deliveries for up to 3 days in live mode, and Shopify retries up to 8 times across 4 hours before removing the subscription. GitHub doesn't retry automatically, but lets you redeliver failed deliveries from the past 3 days.

How can I test a webhook without a server?

Create a free bin on webhooks.sh with no signup, point the provider at its ingest URL or send it a request with curl, and watch requests arrive live in your browser. A free bin lasts 48 hours and takes 100 requests a day, and you can claim it with a Google sign-in to keep the URL.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.