What Is a Webhook? The Beginner's Guide (Events, Payloads, and Webhook vs API)
CodexFuse Team · 2026-11-04T07:03:36.317Z · 13 min read min read
What Is a Webhook? The Beginner's Guide (Events, Payloads, and Webhook vs API)
Every modern app talks to other apps behind the scenes, and the humble webhook carries most of those conversations. If you have ever asked what is a webhook, here is the whole answer in one sentence: a webhook is an automated HTTP request that one system sends to another the moment an event occurs — a payment succeeds, a user signs up, a file finishes uploading — so the receiving app finds out immediately instead of having to ask (Svix). If you want to watch one land in real time before reading further, our free webhook tester gives you a unique URL that captures live deliveries, headers and all. This is webhook explained from a beginner's seat: how webhooks work step by step, what a webhook payload contains, how webhook signature verification keeps them trustworthy, how to test webhooks on your own laptop, and when to reach for a webhook instead of a plain API.
What is a webhook, exactly?
A webhook is an event notification delivered as an HTTP POST: when something happens in one system, that system sends a request to a URL you control, and the request body describes what happened — your app never has to ask whether anything changed (Alchemy). The direction is the whole point. With a normal API, your code calls the service; with a webhook, the service calls your code (dev.to). A webhook connects two parties: the sender watches for events, the receiver is your app exposing a URL, and when an event matches what you subscribed to, the sender makes an HTTP POST request to that URL with a JSON body describing the event while your server answers with a 200 status code to confirm it arrived (Alchemy).
Strip away the marketing language and a webhook is just three things: an event on the provider's side (payment captured, code pushed, sensor threshold crossed), an HTTP POST the provider sends to a URL you registered carrying the event as a JSON body, and your endpoint — a normal route on your server, for example POST /webhooks/payments — that reads the JSON and acts on it. There is no special protocol, no SDK required, no persistent connection (projectech.in). The term itself dates to 2007, when Jeff Lindsay coined webhook from the computer programming term hook (Wikipedia).
How does a webhook work? The 5-step flow
A webhook works in five steps, in the formulation Svix publishes in its webhook fundamentals guide:
- Register. You give the provider a public HTTPS endpoint and pick the event types you want. This is usually done through a dashboard UI or an API call.
- Event. When something you subscribed to happens, the provider queues the event — delivery never blocks its own request handling.
- Deliver. A worker sends an HTTP POST with a JSON body plus identifying and security headers: an event ID, a timestamp, and an HMAC signature.
- Verify and respond. Your endpoint checks the signature, returns a 2xx status within 5 to 30 seconds, and processes the event asynchronously so the response stays fast.
- Retry. If your endpoint returns a 5xx or times out, the provider retries with exponential backoff over 24 to 72 hours, then dead-letters the event (Svix).
Notice the two ideas doing all the heavy lifting: the sender pushes exactly when an event occurs, and the 2xx response is the receipt. No receipt, no certainty — so the sender tries again.
What is in a webhook payload?
A webhook payload is the JSON body that describes the event, plus the HTTP headers around it that carry metadata, proof of authenticity, and timing. Headers often carry metadata like the event type, a signature for verification, and a timestamp — and an event ID or delivery ID identifies the event independently of any single delivery attempt, which matters because the same event can be delivered more than once (Svix). The payload itself contains the actual event data: it might be the full details of the object that changed — a "fat" payload — or a minimal notification that something happened, requiring you to fetch details separately — a "thin" payload (Svix). Whether payloads are fat or thin is a design decision each provider makes.
Here is what a typical illustrative payload looks like, with each piece labeled. (This is a simplified teaching example, not a real provider's schema.)
{
"event_id": "evt_9f3c2a81", // identifies the event across duplicate deliveries
"event_type": "payment.succeeded", // what happened, so one endpoint can route many events
"created_at": "2026-11-04T06:00:00Z",// when the event occurred on the sender's clock
"data": { // the fat payload: the full object that changed
"amount": 4900,
"currency": "usd",
"customer": "cus_51H7..."
}
}
// Headers the sender adds:
// webhook-id: evt_9f3c2a81
// webhook-timestamp: 1762149600
// webhook-signature: v1,<hmac-sha256 hex>
The three headers in that example — an ID, a timestamp, a signature — are the standard anatomy across most providers, even though the exact header names differ from one provider to the next (Svix).
Webhook vs API vs polling: which should you use?
A webhook and an API are complements, not competitors: an API is pull-based — your application requests data when it wants it — while a webhook is push-based — the other system sends data to you when an event occurs. Most integrations use both together, an API for on-demand reads and writes and webhooks for change notifications (Svix). Polling is the third option: your app asks the provider on a timer whether anything changed. It works, but it is wasteful — most of the time the answer is "no, nothing new," and you have burned an API call for nothing (Svix).
| Question | API request | Polling | Webhook |
|---|---|---|---|
| Who starts the exchange? | Your app | Your app, on a timer | The provider |
| When does data move? | When you ask | Every interval, even with no changes | Only when an event occurs |
| Latency | Instant on demand | Up to one interval late | Seconds after the event |
| Waste | None — you asked for it | Most requests return "nothing new" | One request per real event |
| Best for | Reads and writes you initiate | Simple setups where delay is fine | Near-real-time event reactions |
(Comparison structure adapted from the Alchemy and Svix overviews.) So when are webhooks the right choice? They excel when you need to react to events in near real-time but do not need to maintain a persistent connection. For high-frequency, bidirectional communication — a live dashboard feed, a chat stream — WebSockets or Server-Sent Events are the better tools instead (Svix).
How do you verify a webhook signature?
Signature verification is the one security step that matters, because an unverified endpoint is an open API anyone on the internet can invoke. Every serious provider signs webhook payloads with HMAC-SHA256: they hash the raw request body with a secret key only you two share, and send the hex digest in a header (projectech.in). On your side you compute the same hash over the body you received and compare the two values.
Three implementation rules decide whether that check actually protects you. First, hash the raw bytes exactly as they arrived — if your framework parses the JSON and your code serializes it again, spacing and key order can change and the hashes will not match (Alchemy). Second, compare in constant time, so response timing does not reveal how much of a forged signature was correct (Alchemy); GitHub's docs say the same thing explicitly: never use a plain == operator, use something like secure_compare or crypto.timingSafeEqual (GitHub Docs). Third, include a timestamp in the signed material and reject deliveries whose timestamp is too old, because a captured valid payload could otherwise be replayed; the OWASP Webhook Security Cheat Sheet notes a five-minute default tolerance in Stripe's libraries, so keep your clock synchronized with NTP (OWASP).
The header names are provider-specific but the pattern is universal. GitHub sends the HMAC hex digest in an X-Hub-Signature-256 header, always prefixed sha256=, computed over the payload with your webhook secret (GitHub Docs). Stripe sends a Stripe-Signature header in the format t=xxx,v1=yyy,v0=zzz — a timestamp plus signature versions — and signs the timestamp and the body together, which means the request body on your side must be the exact UTF-8 string Stripe sent, without any modifications (Stripe Docs; OWASP). Learn this one flow and you can read any provider's webhook security docs in minutes.
Why is HTTPS not optional for webhooks?
HTTPS is mandatory on every webhook endpoint because the signature only proves who sent the payload — it does not hide it. In the OWASP cheat sheet's exact words: "Require HTTPS on every webhook endpoint and reject plain HTTP. A signature proves authenticity but does not encrypt the payload, so anyone on the network path can read an unencrypted delivery." Enforce TLS 1.2 or higher with a certificate from a trusted certificate authority; Stripe, for reference, validates the subscriber's certificate and only negotiates TLS 1.2 and 1.3 (OWASP).
How do you test webhooks on your laptop?
Your laptop has no public URL, so you need a bridge between the sender on the internet and the receiver on localhost — and there are two standard ones. The fastest is webhook.site: a free, no-setup tool that hands you a unique URL the moment you visit, and anything sent to that URL — webhooks, HTTP requests, form submissions — appears in real time with full details: headers, query parameters, body in JSON, XML, form data, or raw (webhook.site). Its free tier gives URLs that expire after 7 days, capped at 100 requests, with no account required — plenty for inspecting your first few deliveries (webhook.site, as of 2026).
When you want the deliveries to reach code you are actually writing, use a tunnel: during development, a tunneling tool such as ngrok gives a server on your laptop a temporary public URL, so you can register that URL with the provider and watch real events hit your real handler (Alchemy; Svix). The practical workflow is: paste the ngrok URL into the provider's webhook settings as your endpoint, trigger a test event (most dashboards have a "send test" button), and read the captured request before you write a line of handler code.
What happens when a delivery fails or arrives twice?
Delivery is at-least-once, so plan for duplicates from day one. The flip side of retries is that the same event can arrive more than once — a retry can fire even though your endpoint actually processed the original request, for example if it timed out while responding (Svix). Publishers retry on failure — Stripe for up to three days with exponential backoff — so every endpoint receives some events more than once (OWASP).
The fix has two parts. First, record the event ID of each processed webhook and skip any ID you have already seen; retain that record across the publisher's whole retry period (OWASP). Second, make your handler idempotent — charging the customer, sending the email, or updating the row must produce the same result whether it runs once or three times. And when your endpoint receives an event it already processed, skip the work and still return a 200 response, which tells the sender to stop retrying (Stripe Docs).
Where do webhooks show up in tools you already use?
Once you know the pattern you will see it everywhere. GitHub repository webhooks fire on pushes, pull requests, and releases — configure them under a repository's Settings, pick the events, and GitHub delivers a ping event first so you can confirm the endpoint works (GitHub Docs); our GitHub beginner guide walks through repository settings where these live, and the Git beginner guide covers the push events that trigger them. Payment platforms like Stripe send payment_intent.succeeded and friends so your server learns about money movement the second it happens. CI services, chat apps, form builders, and CRMs all speak the same protocol: register a URL, pick events, verify signatures, return 200.
If you outgrow third-party dashboards and want to receive webhooks on infrastructure you control, a small VPS running your endpoint behind a reverse proxy is the classic setup — see what VPS hosting is for the hosting side of that picture. The receiver itself is ordinary web code: one route, one signature check, one queue.
What mistakes do webhook beginners make?
The most expensive mistake is skipping signature verification, even "just for now" — an unverified endpoint is an open door, and testing-only shortcuts have a habit of reaching production (projectech.in). The second is re-serializing the request body before hashing, which silently breaks every signature (Alchemy). The third is doing the real work inside the request handler: if processing takes longer than the sender's 5-to-30-second timeout, the sender marks the delivery failed and retries a job you already finished — verify, acknowledge with 2xx, and process asynchronously instead (Svix). The fourth is assuming each event arrives exactly once, which at-least-once delivery guarantees it will not (Svix; OWASP).
Sources
- Svix — "What is a webhook? How they work and when to use them" (svix.com/resources) — updated 2026-10-22 — definition, the five-step flow verbatim, payload anatomy, retry/dead-letter policy, webhook vs API vs polling, idempotency guidance.
- Alchemy — "What is a webhook?" (alchemy.com/overviews) — fetched 2026-11-04 — definition, HMAC-SHA256 verification mechanics, raw-bytes and constant-time rules, ngrok testing flow, API/polling comparison table.
- Wikipedia — "Webhook" — fetched 2026-11-04 — Jeff Lindsay coined the term in 2007 from the programming term hook.
- GitHub Docs — "Validating webhook deliveries" — fetched 2026-11-04 —
X-Hub-Signature-256header,sha256=prefix, HMAC hex digest, constant-time comparison rule. - Stripe Docs — "Verify webhook signatures" — fetched 2026-11-04 —
Stripe-Signaturet=xxx,v1=yyy,v0=zzzformat, timestamp-plus-body signing, exact-UTF-8-body requirement, skip-and-return-200 duplicate handling. - OWASP — "Webhook Security Cheat Sheet" — fetched 2026-11-04 — HTTPS/TLS 1.2+ requirement with verbatim authenticity-vs-encryption rationale, five-minute timestamp tolerance note, Stripe three-day retry window, event-ID persistence guidance.
- webhook.site — "What is webhook.site" (blog, published 2026-07-15) — fetched 2026-11-04 — unique-URL capture flow, real-time inspection, 7-day expiry and 100-request free-tier cap.
- projectech.in — "Webhooks Explained: A Practical Guide for Students" — updated 2026-10-04 — the three-part mental model, HMAC-SHA256 signing description, never-skip-verification rule.
Frequently Asked Questions
What is a webhook in simple terms?
A webhook is an automatic message one app sends to another when something happens: the sending app makes an HTTP POST request to a URL you provide, with the details of the event in the request body (ifttt.com; Alchemy). You give a service a URL once, and it calls that URL every time an event you care about occurs.
What is the difference between a webhook and an API?
Direction. An API is pull-based — your application requests data when it wants it — while a webhook is push-based — the other system sends data to you when an event occurs. Most integrations use both: the API for on-demand reads and writes, webhooks for change notifications (Svix).
What does a webhook payload contain?
The event data as JSON — the full object that changed ("fat") or a minimal notification ("thin") — plus headers carrying an event ID, a timestamp, and an HMAC signature for verification (Svix). The event ID lets you detect duplicate deliveries of the same event.
How do I verify a webhook signature?
Recompute HMAC-SHA256 over the exact raw request bytes with your shared secret, compare it to the signature header using a constant-time comparison, and reject timestamps older than your tolerance window. Check your provider's docs for the header name — GitHub uses X-Hub-Signature-256, Stripe uses Stripe-Signature (Alchemy; GitHub Docs; Stripe Docs).
How can I test webhooks locally?
Use webhook.site for instant inspection — it gives you a unique URL and shows every delivery in real time, free with a 7-day expiry and 100-request cap — or run ngrok to give your laptop's server a temporary public URL you can register with the provider (webhook.site; Alchemy).

