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
- You give the provider a URL (called the webhook URL, endpoint or callback URL) and pick the events you care about, once.
- An event happens: a payment succeeds, code is pushed, an order is placed.
- The provider sends an HTTP
POSTto 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. - Your endpoint checks the signature and answers with a
2xxstatus fast: GitHub allows 10 seconds, Shopify 5, Slack 3. - If the answer is late or an error, the provider retries on its own schedule, or not at all: GitHub doesn't retry automatically.
- 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.succeededandcheckout.session.completedso you can fulfill an order. - Shipping code: GitHub
pushandpull_requestevents trigger CI in Jenkins or CircleCI, deploy to production or update Jira. - Running a store: Shopify sends
orders/createwhen an order is placed andproducts/updatewhen 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.
| Provider | Example event | Signature header | Answer within | If you miss it |
|---|---|---|---|---|
| Stripe | payment_intent.succeeded | Stripe-Signature | Quickly, before slow work | Retries for up to 3 days in live mode |
| GitHub | issues (opened) | X-Hub-Signature-256 | 10 seconds | No automatic retry; redeliver within 3 days |
| Shopify | orders/create | X-Shopify-Hmac-Sha256 | 5 seconds | Up to 8 retries in 4 hours, then the subscription is removed |
| Slack (Events API) | reaction_added | X-Slack-Signature | 3 seconds | Up to 3 retries: at once, after 1 minute, after 5 minutes |
| Discord (webhook events) | APPLICATION_AUTHORIZED | X-Signature-Ed25519 | 3 seconds, with a 204 | Retries 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.
- Create a free bin in your browser at
view.webhooks.pageor with the firstcurlcommand below (no signup). - Play the provider yourself with the second command, which sends a
POSTwith a small GitHub-styleissuesevent and anX-GitHub-Eventheader. - Open the
view_urlin 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-Signaturealso signs a timestampt, and Stripe's libraries reject anything older than 5 minutes by default, which stops replays. - Answer with a
2xxstatus 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-DeliveryGUID 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.