What is a webhook?
How webhooks deliver events over plain HTTP, how they compare to polling, and what every receiver has to get right.
7 min read · Updated 24 October 2026
A webhook is an HTTP request that one system sends to another when something happens. A payment succeeds, a pull request is opened, a form is submitted: the system where the event occurred makes a request to a URL you gave it, and the body of that request describes the event.
There is nothing special about the request itself. It is usually a POST with a JSON body, sent over HTTPS like any other API call. What makes it a webhook is the direction. Instead of your code asking a service for news, the service calls your code when there is news.
Polling versus webhooks
Without webhooks, the only way to learn about a change in another system is to ask repeatedly. This is polling: every minute, callGET /orders?updated_since=… and see if anything changed. Polling is simple, but it has two costs that grow together.
- Latency. If you poll every five minutes, you learn about an event up to five minutes late.
- Wasted work. Most polls return nothing new, yet both sides still pay for every request. Polling faster reduces latency but multiplies the waste.
Webhooks remove both costs. The sender makes one request per event, at the moment the event happens. The trade-off is that you now have to run a public endpoint that can accept those requests reliably, and that is where most of the work lives.
What a delivery looks like
Here is a representative webhook delivery as it arrives over the network. The details vary by provider, but the shape is common:
POST /webhooks/billing HTTP/1.1
Host: api.example.com
Content-Type: application/json
User-Agent: ExampleBilling-Webhooks/2.0
X-Example-Event: invoice.paid
X-Example-Delivery: 7f9c2ad1-5c1e-4b0e-9a63-1d2f4e8b7c10
X-Example-Signature: sha256=3b1f…
{
"id": "evt_01JB8Z4K2M",
"type": "invoice.paid",
"created": 1729778527,
"data": {
"invoice_id": "in_4821",
"amount_paid": 4900,
"currency": "usd"
}
}Each part has a job:
- The path is whatever you registered. Some teams use one path per provider, others one per event type.
- An event type, either in a header or in the body, tells you what happened so you can route it to the right handler.
- A delivery or event ID uniquely identifies the event. You will need it to detect duplicates.
- A signature header lets you prove the request came from the provider and was not modified. See verifying webhook signatures.
What the receiver must do
Receiving a webhook is easy. Receiving webhooks correctly, every time, takes a few deliberate choices.
Acknowledge quickly
Senders wait a limited time for your response, often a few seconds to a few tens of seconds. If your handler does slow work before responding (calling other APIs, sending email, generating files), you risk a timeout, and the sender will treat the delivery as failed. The reliable pattern is to verify the request, store it or put it on a queue, respond with a 2xx status, and do the real work afterwards.
Expect retries and duplicates
When a delivery fails, most providers retry it later, usually with increasing delays. That means your endpoint will sometimes see the same event more than once: for example when your server processed the event but the response was lost on the way back. Store the event ID when you process an event and skip any ID you have already handled. This makes the handler idempotent, which is the single most important property of a webhook receiver.
Do not rely on order
Retries and parallel delivery mean events can arrive out of order. An order.updated event can land before the order.created event it follows. Use timestamps or version numbers in the payload, or fetch the current state of the object from the provider's API when ordering matters.
Authenticate every request
Your webhook URL is reachable by anyone on the internet. Unless you verify a signature or another credential, an attacker who learns the URL can send fake events. Treat an unverified webhook exactly like an anonymous form submission.
Webhooks compared with other options
| Approach | Who starts the connection | Good fit |
|---|---|---|
| Polling an API | Your system, on a schedule | Low event volume, no public endpoint possible |
| Webhooks | The provider, per event | Server-to-server notifications across organizations |
| WebSockets or streaming | Your system, kept open | Continuous, high-frequency updates, often to browsers |
| Message queues | Both connect to a broker | Systems inside one organization that share infrastructure |
How to start testing a webhook
Before writing a handler, look at real deliveries. Documentation examples are often simplified, and fields appear or disappear depending on account settings and API versions. A practical sequence:
- Create a temporary URL in the Tapwire inspector and register it with the provider as the webhook destination.
- Trigger the event (most providers have a test or resend button) and inspect the exact headers and body.
- Write your handler against that real payload. Copy the request as cURL to replay it against your local server.
- Configure the inspector to return a
500or add a delay, and watch how the provider retries. Now you know how your production endpoint will be treated when it fails.