# What is a webhook

What a webhook is, how it differs from polling an API and from a WebSocket, what a payload and a signature look like, and what you have to build to receive one reliably.

Source: https://docs.webhooker.eu/guides/what-is-a-webhook/

A webhook is an HTTP request that one service sends to a URL you own, the moment
something happens on its side. A customer pays, a pull request is merged, an
order ships — and your server gets a `POST` about it within a second or two.

The name is unhelpful. There is no hook, and it is not really a "web" anything.
It is just a callback: you hand a provider a URL, and the provider calls it.

## The shape of one

Here is a Stripe webhook, trimmed down. Nothing about it is exotic:

```http
POST /hooks/stripe HTTP/1.1
Host: api.acme.dev
Content-Type: application/json
Stripe-Signature: t=1758275400,v1=5257a869e7ecebeda32affa62cdca3fa…

{
  "id": "evt_1PqR2sK8f",
  "type": "invoice.paid",
  "created": 1758275400,
  "data": { "object": { "id": "in_1PqR2s", "amount_paid": 4200 } }
}
```

Three parts matter. The **body** describes what happened. The **signature
header** proves the request came from Stripe and not from someone who found your
URL. And the **status code you return** tells Stripe whether to try again: a
`2xx` means you have it, anything else means you do not.

That last part catches people out. Your response is not a courtesy — it is the
only signal the sender has, and it decides whether the event is retried or
dropped.

## Webhook vs API polling

Both move the same data. The difference is who starts the conversation.

| | Polling an API | Webhook |
| --- | --- | --- |
| Who calls whom | You call the provider | The provider calls you |
| Latency | As long as your interval | A second or two |
| Cost when idle | Every empty poll still costs a request | Nothing |
| What you must run | A scheduler | A public HTTPS endpoint |
| Failure mode | You fall behind quietly | You miss the event entirely |

Polling every minute means a thousand requests a day to learn about three
payments. It also means the news is up to a minute old. A webhook costs nothing
while nothing is happening and arrives almost immediately.

The catch is the last row. A poller that breaks catches up as soon as it comes
back. A webhook endpoint that is down during the one minute an event fires may
never hear about it again, depending on how forgiving the sender is.

In practice most integrations use both: the webhook tells you *something*
changed, and then you call the API to read the authoritative current state. That
also sidesteps out-of-order delivery, because the API always answers with the
latest version.

## Webhook vs WebSocket

Different tools for different traffic.

A webhook is one request per event, to a public URL, with nothing held open in
between. A WebSocket is one connection that stays open, carrying messages both
ways until someone closes it.

Webhooks win for server-to-server events that happen occasionally and have to
survive a restart, like a payment or a build finishing. WebSockets win when
messages are constant and losing one barely matters — a price ticker, a cursor
moving.

If you find yourself sending a webhook every few hundred milliseconds, you
probably want a stream. If you find yourself keeping a WebSocket open all night
to catch two events, you probably want a webhook.

## What you actually have to build

The first version of a webhook handler is four lines and works fine in
development. The production version is a different animal, and shipping the
first one is how most webhook outages start.

**A public HTTPS endpoint.** No `localhost`. Senders will not reach your laptop,
and most refuse plain `http`. During development you need a tunnel or a relay —
see [testing webhooks locally](/tutorials/local-development/).

**Signature verification.** Your URL is public and guessable. Without a
signature check, anyone can post a fake `invoice.paid` and see what your code
does with it. Verifying means recomputing an HMAC over the **raw** bytes you
received, before any JSON parser touches them, and comparing in constant time.
Every provider does this slightly differently — Stripe signs `{timestamp}.{body}`
and puts the result in `Stripe-Signature`, GitHub signs the body alone and sends
`X-Hub-Signature-256`, Shopify does the same in base64.

**A fast response.** Senders time out quickly. Stripe gives you 20 seconds,
GitHub 10. If your handler charges a card, writes to three tables and sends an
email before answering, you will eventually cross the line and the sender will
record a failure for an event you actually processed. Store the payload, answer
`2xx`, do the work afterwards.

**Idempotency.** You will receive the same event twice. Sometimes the sender
retries after a response that got lost on the way back; sometimes it just sends
duplicates. Store the event id and skip anything you have already handled.

**Somewhere to put failures.** Your endpoint will be down at some point — a
deploy, a bad migration, an upstream that stopped answering. What happens to the
events that arrive during those four minutes? If the answer is "nothing, they
are gone", you have a reconciliation job in your future.

**A record of what arrived.** When a customer says the webhook never came, you
need to know whether it arrived and your handler broke, or it never arrived at
all. Those have opposite fixes, and you cannot tell them apart from your
application logs alone.

## Retries are not guaranteed

There is no standard here, and the variation is wider than most people expect:

- **Stripe** retries with exponential backoff for about three days.
- **GitHub** does not retry. One attempt, and you can re-send it by hand from
  the repository settings.
- **Shopify** retries 19 times over 48 hours, then removes the subscription.
- Plenty of smaller providers try once and never mention it again.

So "the sender will retry" is not a plan. It is a property of one specific
provider that you have to look up, and it changes when you add the next
integration.

## Where a webhook gateway fits

A gateway sits between the sender and your app. It accepts the request, checks
the signature, stores the whole thing, and only then forwards it to your handler
on its own schedule.

The point is that accepting and processing become separate problems. The sender
always gets a fast `200`, so it never starts its own retry storm and never
disables your endpoint for being unreliable. Your handler can be down for an
hour, and the events wait in a queue instead of evaporating. When something
fails, you have the raw request — headers, body, response, latency per attempt —
instead of a guess.

That is what Webhooker does: one ingest URL per sender, verification before
anything is stored, and delivery to your endpoints with retries, filters and
a dead-letter queue. The [quick start](/guides/quickstart/) goes from an empty
account to a delivered event in four steps.

## Common questions

**Can a webhook return data to the sender?** Only a status code and a short
body, and almost no provider reads the body. Treat the response as an
acknowledgement, not a channel.

**What if I need the events in order?** Assume you will not get them in order.
Retries alone will shuffle them. Sort on a sequence number or timestamp inside
the payload, or re-read the current state from the API and ignore arrival order.

**Is a webhook secure over plain HTTP?** No. The payload is readable in transit
and the signature does not fix that. Use HTTPS.

**How big can a payload be?** It depends on the sender, and it is rarely
documented. A GitHub push with hundreds of commits can run past a megabyte,
which is enough to hit a size limit you did not know you had.

## Next

- [Quick start](/guides/quickstart/) — an ingest URL and a delivered event in four steps.
- [Verify our signature](/deliver/verify-signatures/) — working verification code in Node, Python and Go.
- [Inbound verification](/receive/verification/) — the signature schemes Webhooker checks for you.
