What Is a Webhook? A Practical Guide for Developers
A webhook is an HTTP request that one system sends to another, automatically, the moment a specific event happens. Instead of your application repeatedly asking a service "has anything changed yet?", the service tells you the instant something does. It is one of the simplest and most widely used patterns for connecting software across the internet.
The core idea: a callback over HTTP
The clearest way to understand a webhook is as a reversed API call. In a normal API request, your code initiates the call: you send a request to a provider and wait for a response. With a webhook, the provider initiates the call to you. You register a URL you control, and when a relevant event occurs, the provider sends an HTTP request (almost always a POST) to that URL carrying details about what happened.
Because of this inversion, webhooks are often described as "reverse APIs" or "HTTP callbacks." The word callback is apt: you hand a service a phone number (your URL) and ask it to call you back when there is news, rather than calling it every few seconds to check.
A concrete example: a payment processor sends a webhook to your server when a charge succeeds, fails, or is refunded. A version-control host sends one when code is pushed or a pull request is opened. A messaging platform sends one when a user sends a message to your bot. In every case, the event originates with the provider, and your endpoint is the recipient.
What a webhook request actually looks like
A webhook is an ordinary HTTP request, so it has the same parts as any other: a method, a target URL, headers, and a body. The body, called the payload, carries the event data and is most commonly formatted as JSON. Providers typically include headers that identify the event type and a signature used to verify authenticity.
A simplified payload from a payment event might look like this:
POST /webhooks/payments HTTP/1.1
Host: your-app.example.com
Content-Type: application/json
X-Event-Type: charge.succeeded
X-Signature: t=1699999999,v1=5257a8...
{
"id": "evt_1a2b3c",
"type": "charge.succeeded",
"data": {
"amount": 4200,
"currency": "usd",
"customer": "cus_98765"
}
}
When you build the receiving end, you write an endpoint that reads the payload, confirms the request is genuine, performs whatever action the event requires, and returns a response. Pasting a sample body into a JSON formatter while you develop makes it far easier to see the structure you need to parse.
Webhooks vs. polling vs. WebSockets
Webhooks are one of several ways to learn about changes in a remote system. The alternatives each fit different needs, and choosing well matters.
| Pattern | Who initiates | Best for |
|---|---|---|
| Polling | Your app repeatedly asks the provider | Simple setups, when you cannot expose a public URL |
| Webhook | Provider sends you a request per event | Event-driven, infrequent or unpredictable updates |
| WebSocket / SSE | A persistent connection streams data | High-frequency, continuous, low-latency streams |
Polling wastes work: most requests return "nothing new," and you still pay the latency between checks. Webhooks remove that waste by pushing only when something happens, which is why they suit events that are sporadic, like a payment or a deployment. For continuous high-frequency data such as live chat or market ticks, a persistent connection is usually the better fit; the trade-offs are covered in our comparison of WebSockets vs. Server-Sent Events.
Verifying that a webhook is genuine
Because your webhook URL receives unauthenticated HTTP requests from the open internet, anyone who learns the URL could send forged events. Verifying authenticity is the single most important thing to get right when receiving webhooks.
The standard mechanism is a signature. The provider shares a secret key with you. For each event, it computes a hash-based message authentication code (HMAC) over the raw request body using that secret, and sends the result in a header. Your server recomputes the same HMAC over the body it received and compares the two values. If they match, the request genuinely came from the provider and was not altered in transit.
Two details are easy to miss. First, you must hash the raw request body exactly as received, before any parsing or re-serialization, because re-encoding can change bytes and break the comparison. Second, compare signatures with a constant-time comparison function to avoid timing side channels. You can experiment with computing these digests using an HMAC generator to confirm your implementation produces the expected output. Some providers instead send a signed token; if yours uses JSON Web Tokens, our explainer on JWTs covers how those are validated.
Designing a reliable webhook receiver
Senders generally treat any non-2xx response, or a slow one, as a failure and retry the delivery. That single fact drives most receiver best practices.
- Respond quickly. Acknowledge receipt with a 2xx status as soon as you have stored the event, then do the heavy work asynchronously. Long processing inside the request risks a timeout and a retry.
- Expect duplicates. Retries and network hiccups mean the same event can arrive more than once. Make handling idempotent by recording each event ID and ignoring ones you have already processed.
- Do not assume ordering. Events may arrive out of order. Rely on timestamps or sequence numbers in the payload rather than arrival order.
- Validate before trusting. Verify the signature first, and reject anything that fails before touching the payload.
Retries also interact with rate limiting on your side. If your endpoint returns 429 or throttles aggressively, a provider's backoff schedule can delay legitimate events significantly; our guide to rate-limit strategies for APIs explains how to set sensible limits.
Common pitfalls and how to avoid them
Most webhook problems trace back to a handful of mistakes. Parsing the body before verifying the signature defeats the whole security model. Returning a 2xx only after slow work has finished causes timeouts and storms of retries. Ignoring duplicate deliveries leads to double-charging, double-emailing, or duplicate records. Logging full payloads can leak secrets and personal data, so redact sensitive fields before writing logs.
Local development is its own hurdle, since providers cannot reach a server running on localhost. A tunneling tool that exposes your machine on a temporary public URL solves this, letting real events reach your code while you debug. To test handler logic without waiting for real events, generate representative payloads with an API mock generator and post them to your endpoint directly. With signature verification, idempotency, and fast acknowledgment in place, a webhook receiver becomes a dependable backbone for event-driven integrations.
Frequently Asked Questions
They are related but inverted. With a normal API call, your code sends a request to a provider and waits for a response; with a webhook, the provider sends a request to a URL you control whenever an event occurs. A webhook is essentially a provider-initiated HTTP callback.
Webhooks carry event data in a request body, and POST is the standard method for sending a body to a server. The receiver reads that payload, acts on it, and returns a status code. Some providers use other methods, but POST with a JSON body is by far the most common.
Most providers sign each request with an HMAC computed over the raw body using a shared secret, then send the result in a header. Recompute the same HMAC on your side and compare it using a constant-time comparison. You can check your digest output with the /tools/hmac-generator tool.
Most providers retry failed deliveries on a backoff schedule, so a brief outage usually does not lose events, though policies vary by provider. Because retries can deliver the same event twice, your handler should be idempotent and process each event ID only once.
Providers cannot reach localhost, so use a tunneling tool that gives your machine a temporary public URL to receive real events. To test handler logic without live events, post sample payloads to your endpoint, for example ones built with the /tools/api-mock-generator.