What Is a Webhook? How It Works, Explained

A plain-English guide to webhooks: how the event-to-POST flow works, how they differ from APIs and polling, where they are used, and how HMAC signatures keep them secure.

GitHub's official social-preview graphic for its webhooks documentation page
GitHub's social-card graphic for its webhooks documentation. Image: GitHub.

A webhook is an automated message a web application sends the instant something happens inside it — a payment clears, a code commit lands, a message arrives — delivered as an HTTP POST request to a URL you provide in advance. Instead of your application repeatedly asking "has anything changed yet?" through an API, the provider pushes the data to you the moment the event occurs. That one difference, push instead of pull, is why webhooks have become the default way that payment processors, chat platforms, code-hosting services and e-commerce systems notify other software of real-time events.

Key facts
  • Developer Jeff Lindsay coined the term "webhook" in 2007, describing it as a "hook" for the web — a user-defined HTTP callback, according to Wikipedia.
  • A webhook is almost always an HTTP POST request containing a JSON payload, sent to an endpoint URL you registered with the provider.
  • Providers expect a fast 2xx response — GitHub requires one within 10 seconds — and treat anything else as a failed delivery.
  • Failed deliveries are typically retried automatically; Stripe retries for up to three days using exponential backoff.
  • Because anyone who finds your endpoint URL could try to fake an event, every major provider signs its payloads with an HMAC signature that your server must verify.

What is a webhook?

A webhook is a way for one application to notify another, automatically, when a specific event occurs — without the receiving application having to ask. Mechanically, it is nothing exotic: it's an outbound HTTP request, almost always a POST, that a server fires toward a URL you configured ahead of time, carrying details about the event in its body. Wikipedia traces the term to a May 2007 blog post by programmer Jeff Lindsay, who adapted it from the older "hook" concept in software — a point in a program where other code can attach custom behavior — and applied it to the web.

In practice, you set up a webhook by registering an endpoint — a public HTTPS URL on your server — with a provider such as a payments platform, a code host, or a messaging service, and telling it which events you want to hear about. From that point on, whenever a matching event happens on the provider's side, its servers make an HTTP call to your URL, and your server is responsible for being there to receive it. This is the same basic model used for linking content on sites like GitHub Actions CI/CD pipelines, which are themselves frequently triggered by webhook-style events such as a push or a pull request.

Webhooks vs. a normal API call vs. polling

Most developers meet webhooks after they've already worked with a conventional REST API, where your application is the one making requests — "give me the current status of order 4821" — and the provider replies. GitHub's own documentation contrasts this directly with polling, which it defines as "calling an API intermittently to check whether data is available." Polling works, but it means making requests on a schedule whether or not anything has actually changed, which wastes requests, burns through rate limits, and introduces lag between an event happening and your system noticing it. A webhook flips that relationship: you express interest once, when you create the subscription, and the provider contacts you only when there's something to report. If you're unfamiliar with how a basic API call or a single request-response cycle works in the first place, this plain-English guide to how an API works is a useful primer before going further into webhooks specifically.

ApproachWho initiates the requestTimelinessResource cost
Polling an APIYour application, on a repeating scheduleDelayed by the polling intervalHigh — most requests return "nothing new"
WebhookThe provider, only when an event occursNear real-timeLow — one request per real event
Standard (synchronous) API callYour application, on demandImmediate, but only when you askModerate — depends on call volume

How the request/response flow actually works

Strip away the branding on any individual provider's docs and the webhook lifecycle follows the same basic sequence everywhere:

  1. Event. Something happens inside the provider's system — a customer's card is charged, a pull request is merged, an inbound text message arrives.
  2. HTTP POST. The provider's servers build a payload describing that event and send it as an HTTP POST request to the endpoint URL you registered.
  3. Endpoint. Your server, listening at that URL, receives the request. Stripe's documentation is explicit that this must be a publicly reachable HTTPS address — a webhook can't be delivered to a machine sitting behind a firewall with no public endpoint.
  4. Payload. Your application reads the JSON body, typically verifies the sender's signature first, and then parses out the event type and data it needs.
  5. Response code. Your server replies with an HTTP status code. A 2xx response tells the provider the delivery succeeded; anything else — a timeout, a 4xx, or a 5xx — is treated as a failure.
  6. Retries. If the delivery failed, most providers will attempt to resend the same event later, usually several times, before giving up.

Both Stripe and GitHub stress the same operational rule at step five: respond fast, before doing any real work. Stripe's guidance is to "quickly return a successful status code (2xx) before any complex logic that could cause a timeout," for example returning a 200 before you've actually finished updating a customer's invoice in your own accounting system. GitHub is more specific still, telling developers their server "should respond with a 2XX response within 10 seconds of receiving a webhook delivery," after which it "terminates the connection and considers the delivery a failure." The usual pattern is to acknowledge receipt immediately, then hand the payload off to a background queue for the actual processing.

What's inside a webhook payload

A webhook payload is normally a JSON document describing one event. Stripe's events, for instance, carry an event type such as payment_intent.succeeded along with a data object holding the relevant resource — the specific PaymentIntent that succeeded, in that case. GitHub's deliveries work the same way: an HTTP POST body containing structured JSON about the triggering activity, such as a push or an issue comment, sent to the URL configured on the webhook. Because the sender and receiver have to agree in advance on that structure, most API providers publish a full catalog of event types and their payload shapes in their developer documentation, and you typically filter, when you first set up the webhook, to only the specific event types your integration actually needs.

Two header-level details matter for almost any implementation. First, the request carries a content type of JSON, so your framework needs to read it before anything mutates or re-serializes the raw body — Stripe warns explicitly that any manipulation of the raw request body will break signature verification later. Second, the sender usually attaches extra headers beyond the body itself, most importantly a signature header (Stripe-Signature on Stripe's platform, X-Hub-Signature-256 on GitHub's) that your server uses to confirm the request is authentic before trusting anything inside it.

Response codes and retry behavior

Because a webhook delivery is just an HTTP request, the status code your endpoint returns carries real meaning for the sender, not only for your own logs. The general pattern, consistent with how HTTP status codes are defined by the Mozilla Developer Network and applied in provider webhook systems:

Status codeMeaningWhat a sender typically does
200 / 201 / 204 (2xx)Request succeeded; your endpoint accepted the eventMarks the delivery as successful, no retry
3xx (redirect)Your endpoint tried to redirect the requestStripe treats redirect responses to webhooks as failures outright
400 Bad RequestThe server can't process a malformed requestUsually not retried — the payload or handler needs fixing
401 / 403Authentication or authorization failedNot retried until credentials or access are corrected
404 / 410Endpoint not found, or gone permanentlySenders may eventually stop delivering to a 404/410 endpoint
429 Too Many RequestsReceiver is rate-limiting the senderSender backs off and retries later
5xx (server error)Receiver's server failed or timed outRetried automatically, typically with exponential backoff

Retry policies differ by provider but follow a similar shape: give the receiving server multiple chances, spread further and further apart, before giving up entirely. Stripe says it "attempts to deliver events to your destination for up to three days with an exponential back off in live mode," and separately offers a manual resend from the dashboard for up to 15 days, or via its CLI for up to 30 days, after the event was created. GitHub's documentation describes failed deliveries as something you can manually redeliver from a repository's webhook settings, in addition to whatever automatic retry behavior applies to a given delivery. The practical lesson is the same across platforms: your endpoint will likely receive the same event more than once over its lifetime, whether due to an automatic retry or a manual resend, so it needs to handle duplicates gracefully — Stripe's own advice is to log event IDs you've already processed and ignore repeats.

Common uses: payments, chat, CI/CD and e-commerce

Webhooks show up anywhere one system needs to tell another about something that just happened, asynchronously and without a human watching a dashboard. A few of the most common categories:

  • Payments. Stripe's documentation lists exactly this use case first: webhooks let an integration "automatically trigger reactions" to events like a bank confirming a payment, a customer disputing a charge, or a recurring subscription payment succeeding — all things that happen on a timetable the merchant's own server can't predict.
  • Chat and messaging. Twilio's developer docs describe an inbound SMS to one of its phone numbers as triggering a webhook to the receiving application, and note that products like Twilio Conversations and Sync rely on webhooks the same way. Twilio further splits these into "instruction" webhooks, where your app must answer with instructions for what Twilio should do next, and purely informational ones, such as notice that a call recording has finished.
  • CI/CD and source control. A push to a repository, a new pull request, or a completed build are classic webhook triggers; GitHub's webhook system underlies much of the automation described in how GitHub Actions CI/CD works, where an event on the repository kicks off a pipeline elsewhere.
  • E-commerce. Order placed, inventory updated, shipment status changed — storefronts and fulfillment systems trade these events constantly with payment processors, shipping carriers and marketing tools, almost always over webhooks rather than polling.

Webhook security: why signature verification matters

A webhook endpoint is, by definition, a public URL that accepts incoming POST requests — which means anyone who discovers it could try sending a fake event to trigger an action in your system, such as marking an unpaid order as paid. Stripe's documentation states this risk plainly: "Without verification, an attacker could send fake webhook events to your endpoint to trigger actions like fulfilling orders, granting account access, or modifying records." Its fix, shared in substance by nearly every major provider, is cryptographic signing.

Stripe signs every webhook event with an HMAC using SHA-256, placing the result in a Stripe-Signature header alongside a timestamp; your server recomputes the same HMAC over the raw request body using a shared secret and compares it, using a constant-time comparison, to the value Stripe sent. The timestamp exists specifically to blunt replay attacks — someone capturing a legitimate signed request and resending it later — and Stripe's own libraries reject anything outside a five-minute tolerance window by default.

GitHub's approach is functionally identical: it signs each delivery with HMAC-SHA256 over the raw payload body, using a secret you configure on the webhook, and sends the resulting hex digest in an X-Hub-Signature-256 header prefixed with sha256=. GitHub's documentation walks through computing that same HMAC on your end and comparing it with a constant-time function rather than a simple equality check, which can otherwise leak timing information to an attacker. An older X-Hub-Signature header using SHA-1 still exists for backward compatibility, but GitHub recommends the SHA-256 version. This same cryptographic approach — a shared secret, an HMAC over the request body, a header carrying the result — is conceptually similar to the token-based trust that underpins how OAuth 2.0 secures API access, even though the two mechanisms solve different problems.

Signature checks aside, Stripe also recommends IP allowlisting — restricting your firewall to only accept webhook requests from the sender's published IP ranges — as a second layer, and requires TLS 1.2 or higher on the connection itself. The common thread across every provider's advice is the same: never trust a webhook payload just because it arrived at the right URL.

Best practices for handling webhooks reliably

A handful of habits separate a webhook integration that holds up in production from one that quietly drops events:

  • Acknowledge first, process second. Return your 2xx response immediately, and push the real work — updating a database, sending a notification — onto an asynchronous queue, exactly as Stripe and GitHub both recommend.
  • Verify every signature. Reject anything that doesn't match before you read a single field out of the payload.
  • Deduplicate by event ID. Expect to see the same event more than once, due to retries or manual resends, and make your handler idempotent.
  • Don't assume ordering. Stripe explicitly warns that it "doesn't guarantee the delivery of events in the order that they're generated," so an integration shouldn't depend on one event always arriving before another.
  • Subscribe only to what you need. Listening for every event type available puts unnecessary load on both sides of the connection.
  • Keep an eye on delivery logs. Most providers expose a dashboard of recent deliveries, their response codes, and pending retries, which is usually the fastest way to diagnose a silently broken integration.

Getting started with your first webhook

If you're building your first webhook integration, the fastest path is usually to pick the single provider you're already working with — a payments platform, a code host, a messaging API — and follow its own webhook quickstart, since the exact signing scheme, retry window and payload shape vary provider to provider even though the underlying pattern doesn't. Most platforms, including Stripe and Twilio, offer a local tunneling or CLI-based testing tool so you can receive real webhook deliveries on a development machine before you have a public HTTPS endpoint ready. From there, the build order is consistent: stand up an endpoint that returns a fast 2xx, add signature verification before trusting the payload, make your handler safe to run twice on the same event, and only then wire in the business logic the webhook is actually there to trigger.

Frequently asked questions

What is a webhook in simple terms?

A webhook is an automated HTTP request, almost always a POST, that one application sends to a URL you provide whenever a specific event happens, so you receive the data immediately instead of having to ask for it.

How does a webhook differ from an API call?

With a normal API call, your application contacts the provider and asks for data. With a webhook, the provider contacts your application automatically the moment an event occurs, without you asking.

Is a webhook the same thing as polling?

No. Polling means your application repeatedly calls an API on a schedule to check for changes, which wastes requests and adds delay. A webhook eliminates that by having the provider push the event to you only when it actually happens.

What happens if my server doesn't respond to a webhook?

If your endpoint doesn't return a successful 2xx status quickly enough, the provider treats the delivery as failed and typically retries it automatically for a period of time, such as Stripe's up to three-day retry window, before giving up.

How do I verify that a webhook request is genuine?

Providers like Stripe and GitHub sign each webhook payload with an HMAC-SHA256 signature sent in a header (Stripe-Signature or X-Hub-Signature-256). Your server recomputes that HMAC using a shared secret and the raw request body, then compares it with a constant-time check before trusting the payload.

What are common real-world uses of webhooks?

Webhooks are widely used for payment notifications (Stripe), inbound chat and SMS messages (Twilio), CI/CD triggers like code pushes and pull requests (GitHub), and e-commerce events such as new orders or inventory changes.

Sources

More on Webhook →WebhooksAPIsWeb DevelopmentDevOpsPaymentsSecurity
Felix Moreau
Written byFelix Moreau

Felix Moreau writes Pandromeda's software coverage and how-to guides. He covers Windows, macOS and Linux updates, the apps people rely on, emulators and developer tools, and turns official documentation into clear, numbered steps that work on the current version.

More from Software & Guides

See all