What is a webhook?
Webhooks vs polling: the inverse of an API call, when to use them, and how the receiving endpoint should look.
A webhook is an HTTP endpoint that another service calls when something happens. It is the inverse of a normal API call: instead of you asking the upstream "did anything change?", the upstream tells you the moment it does.
The mental model: a regular API call is a phone call you place. A webhook is a phone number you publish, and the upstream calls you. You give them a URL on your server, they POST a JSON body to it whenever a relevant event happens, and your server reacts.
Why webhooks exist
The alternative is polling: every minute, you callGET /payments?status=succeeded and look for new rows. Polling works, but it has three downsides:
- Latency. You only learn about events on your polling interval. A 1-minute interval averages 30 seconds of delay.
- Cost. Most polls return nothing new. You burn rate-limit budget and CPU for negative results.
- Scaling. More clients mean more polling traffic. The upstream eats requests for state that has not changed.
Webhooks fix all three. The upstream sends one HTTP call per real event. You learn within milliseconds. No polling traffic.
What a webhook payload looks like
When the event fires, the upstream sends an HTTP POST to the URL you registered. The body is JSON describing what happened:
POST /webhooks/payment-succeeded HTTP/1.1
Host: yourapp.example.com
Content-Type: application/json
X-Signature: sha256=abc...123
{
"id": "evt_5g7H4Z",
"type": "payment.succeeded",
"occurred_at": "2025-09-01T12:34:56Z",
"data": { "amount": 4900, "currency": "INR" }
}
Three things you should expect in every webhook:
- A unique event id (
idhere). Use it to dedupe in case the upstream retries. - A type (
"payment.succeeded"). Branch on it; one endpoint usually receives many event types. - A timestamp of when the event happened. Useful for detecting replays.
Receiving a webhook
Your endpoint is just a regular POST handler. The two important rules:
- Acknowledge fast. Send 200 OK as soon as you have validated and queued the work. Most webhook providers retry on non-2xx, and many have aggressive timeouts (5-15 seconds). Do not run heavy logic in the request handler; put a job on a queue and ack.
- Verify the signature. Anyone on the internet can hit your URL. The provider signs each request with a shared secret (usually HMAC-SHA256 over the body). Verify the signature before trusting the payload.
app.post("/webhooks/payment-succeeded", express.json(), (req, res) => {
// 1. Verify the signature first
const signature = req.headers["x-signature"];
if (!verifyHmac(req.body, signature, SECRET)) {
return res.status(401).end();
}
// 2. Acknowledge fast, then do real work async
enqueueJob("payment-succeeded", req.body);
res.status(200).end();
});
Idempotency
Webhook providers retry. A network blip, your server returning 500, a slow ack: any of these triggers a retry. Your handler can receive the same event id twice. Dedupe by event id at the database level: insert a row keyed onevent_id, and on duplicate-key error, do nothing and ack.
Replay attacks
An attacker who captures one webhook payload can replay it. Two defences:
- Timestamp the signature. Include the request time in the signed payload, and reject requests older than 5 minutes.
- Idempotency. If you already processed that event id, replays are no-ops anyway.
Stripe's webhook signing scheme (Stripe-Signature) does both. Mirror the pattern when you publish your own.
Local development
Webhook URLs need to be reachable from the public internet. During development, run a tunnel:
ngrok http 3000is the original tunnel. Public URL points at your laptop.cloudflared tunnelis free, fast, and integrates with Cloudflare.tailscale funnelroutes through Tailscale's network.
Most webhook providers also have a "test" / "send sample" button in their dashboard; use it to develop your handler before subscribing to real events.
Webhooks vs server-sent events vs WebSockets
Three patterns for "the server pushes to the client":
- Webhooks. Upstream → your server. One event per HTTP POST. Best for backend-to-backend integration.
- Server-sent events. Server → browser, over a long-lived HTTP connection. Best for streaming notifications to a UI (chat, live scores).
- WebSockets. Bidirectional, server ↔ browser. Best when both ends need to push.
If you are integrating two backend services, webhooks are almost always the right answer. If you are pushing data to a browser tab, use SSE or WebSockets.
Does Go REST have webhooks?
Not currently. Go REST is a synchronous fake-data API for testing client code. Webhooks would mean every project gets an event firehose, which is not the use case. If you want to test how your code receives webhooks, use a tool likerequestbin.com or build a small endpoint and trigger events from your own backend.
More primers
What is a REST API?
A plain-English explanation of REST, HTTP methods, resources, and why almost every modern web service speaks this style.
What is JSON?
JSON syntax in 5 minutes: objects, arrays, strings, numbers, why it replaced XML, and the gotchas that bite beginners.
What is HTTP?
Request/response, headers, methods, status codes - the protocol the web has run on since 1991.