← Blog/Webhooks
What is a webhook?
A webhook is an HTTP callback that pushes event data to your URL. Learn how webhooks work, how they differ from APIs, and how to test them locally.
When a charge succeeds or a pull request opens, the other system does not wait for you to poll. A webhook is that push: an HTTP request to a URL you registered, fired the moment the event happens. This article explains what a webhook is, how it differs from an API, how delivery actually works, and how to inspect a live payload before you write a handler.
What is a webhook?
A webhook — also written web hook — is an HTTP callback. You give a source application a
URL, and that application sends an HTTP request to it when a named event occurs. The request
is almost always POST. The URL is your webhook endpoint.
The body of the request is the payload. It is usually JSON. It names the event, identifies the object, and carries enough fields for your app to act: an amount, a commit SHA, a chat update, an order id.
People call webhooks reverse APIs or push APIs because the source initiates the call. You
are not fetching /events on a timer. You are listening. That is the contract behind most
event-driven SaaS integrations.
Here is a small payload a payments provider might POST to
https://api.example.com/webhooks/payments:
{
"id": "evt_01h8k2",
"type": "payment.succeeded",
"created": "2026-09-03T18:12:04Z",
"data": {
"amount": 4999,
"currency": "usd",
"customer_id": "cus_9f3a"
}
}Your endpoint receives that POST, checks that it really came from the provider, returns a success status quickly, and then sends a receipt, opens access, or writes a row. The HTTP callback is the notification. Your code is the reaction.
Webhook vs API
Webhooks and APIs both move data over HTTP. They differ in who starts the conversation.
A traditional API is pull. Your application decides when to GET or POST. If you need
to know whether a customer paid, you call GET /charges/:id until the status changes. That
pattern is polling. It is easy to implement and expensive in the idle case: most requests
return “nothing new,” and you only learn about the event at the next interval.
A webhook is push. The source POSTs to your webhook endpoint as soon as the event fires. You do not ask whether anything changed. You are told.
- API (pull): you choose the time; you keep asking; updates are delayed by your poll interval.
- Webhook (push): the source chooses the time; one request per event; delivery is near real-time.
You still need APIs. Creating a checkout session, registering the webhook URL itself, and fetching a missing object are API calls. Webhooks tell you that something happened; APIs let you act on purpose. A billing integration typically uses the API to start a subscription and a webhook to learn that this month’s invoice was paid.
How webhooks work
The lifecycle is the same across Stripe, GitHub, Telegram, and most other providers.
- Register. You paste a public HTTPS URL into the provider and subscribe to event types
(sometimes called topics), such as
payment_intent.succeededorpush. - Event. Something happens in the source: a payment clears, a commit lands, a shopper places an order.
- Deliver. The source builds an HTTP request — method, path, headers, body — and POSTs it to your webhook endpoint. Many providers include a signature header so you can authenticate the body.
- Respond and process. Your server verifies the request, returns a 2xx within the provider’s timeout, and then does the real work. Returning 2xx means “I received it,” not “I finished every side effect.”
Most deliveries are POST with JSON in the body. A few providers use GET and put data in
the query string, or send form-encoded bodies. Your endpoint has to accept the method and
content type the docs specify.
Some platforms require a handshake or challenge before live traffic starts. They send a one-time request you must answer so they know the URL is valid and willing to receive events. Until that succeeds, you will not see production deliveries.
You can watch this loop without shipping a handler. Give the provider a public HTTPS URL, trigger a test event (or send a POST yourself), and read the method, headers, and body.
Open the webhook inspector
The guest inspector records what arrived and answers 204 No Content. It does not forward to
localhost. That is enough to answer “did the provider actually send this?” before you debug
your app.
What a webhook is used for
Wherever one product must tell another product that an event happened, you will find webhooks.
Payments and billing. Stripe, PayPal, and similar processors send webhooks for successful charges, failures, refunds, disputes, and subscription changes. Those events provision access, send receipts, or stop a dunning cycle. Walk through a Stripe endpoint on a public URL in Test Stripe webhooks locally.
Version control and CI. GitHub and GitLab POST when code is pushed, a pull request opens, or an issue is filed. CI systems start builds from those deliveries. Test GitHub webhooks locally covers ping and push events against a Mercur URL.
Bots and messaging. Telegram can POST chat updates to your webhook instead of making you
poll getUpdates. Slack and Discord use inbound webhooks for notifications the other way —
your app POSTs into a channel. Create a Telegram bot with webhooks
runs that flow to a local Node server.
E-commerce. Shopify and WooCommerce notify you about orders, inventory, and fulfillment so warehouse and accounting tools do not scrape the admin UI.
Email and SMS. Twilio and SendGrid tell you when a message is delivered, opened, or bounced. Without webhooks you would poll status APIs and still be late.
The payload shape changes. The contract does not: an event in one system becomes an HTTP request to a URL you control.
Consuming webhooks
Treat incoming webhooks as ordinary HTTP, then add the constraints providers actually have.
At-least-once delivery. You will see the same event more than once — retries after a timeout, a second endpoint, or a dashboard “resend.” Make handlers idempotent. Store the provider’s event id and skip work you already completed. A duplicate is usually the provider doing its job, not Mercur replaying on its own; see Duplicate webhook.
Short timeouts. Many providers wait one to five seconds for a response. Return 2xx as soon as the payload is durable. Send email, call another API, or generate a PDF in a background job. If you do all of that on the request thread, you will hit webhook timeouts.
Retries follow your status code. A 2xx stops retries. A 5xx or a timeout usually means
the provider will try again. A 4xx is more ambiguous — some senders retry, some mark the
endpoint failing and stop. Guest Mercur always answers 204, so a 4xx or 5xx in a provider
dashboard is your handler (or a URL that is not the inspector).
No throughput cap from the sender. A product launch or a replay can burst. If you process synchronously, you will drop deliveries or time out. Persist first, then drain a queue.
No ordering guarantee. An updated event can arrive before created. Payloads usually
include a timestamp; use it to ignore stale events when order matters.
Security
A webhook endpoint is a public URL. Anyone who finds it can POST. Authenticity comes from verifying the request, not from hiding the path.
Providers sign the raw body with a shared secret (HMAC) and send the digest in a header such
as Stripe-Signature or X-Hub-Signature-256. Your handler must hash the exact bytes it
received and compare the digest. If they do not match, reject the request. Whitespace,
encoding, and parsing the JSON before hashing are common reasons verification fails;
Webhook signature verification failed walks
through that symptom.
Keep the signing secret in your application’s environment. Mercur does not verify signatures for you. It stores the raw body and headers so you can compare what the provider sent with what your verifier hashed.
Always register an HTTPS webhook endpoint. Mercur URLs are HTTPS. Do not point a provider at
http://.
Debug webhooks locally
The hard part of learning what a webhook is, in practice, is seeing one. Providers cannot
POST to 127.0.0.1. You need a public URL that either records the request or forwards it to
a process on your machine.
Start with inspection. Open the online webhook inspector, copy the
temporary HTTPS URL, and paste it into a provider dashboard or a curl command. Guest mode
needs no account. It records method, path, headers, and body, then returns 204 No Content.
Nothing reaches localhost. Unused guest URLs expire after idle time.
When your handler must run on a local port, create a free Mercur account, start an Endpoint aimed at that port, and connect the macOS app or the npm CLI. Point the provider at the Endpoint URL. Mercur then proxies the live POST to your app so signature checks and business logic run on the real bytes.
Forwarding needs a connected agent — the macOS app or the npm package @mercur_dev/cli.
Step-by-step inspect → forward → replay is in How to test webhooks. Product setup lives in Receive a webhook.
Open the webhook inspector
Frequently asked questions
What is a webhook?
A webhook is an HTTP callback: a source application sends a request, usually POST, to a URL you registered when a specific event occurs. The request body is the payload — typically JSON describing the event — so your app can react immediately instead of polling an API.What is the difference between a webhook and an API?
An API is pull: your code requests data when it wants it. A webhook is push: the other system sends an HTTP request to your endpoint as soon as something happens. Most integrations use both — the API for actions you start, webhooks for events you must handle.How do webhooks work?
You register a webhook endpoint and subscribe to event types. When an event fires, the provider POSTs a signed payload to that URL. Your server verifies the request, returns a 2xx quickly, and processes the event, usually asynchronously so you stay inside the provider's timeout.What is a webhook used for?
Providers use webhooks to notify you about payments, code pushes, chat messages, orders, and email delivery. Stripe, GitHub, Telegram, Shopify, and Twilio all follow the same pattern: an event in their system becomes an HTTP request to yours.How do you test a webhook locally?
Providers cannot reach localhost. Give them a public HTTPS URL, inspect method, headers, and body, then forward to a local port when your handler is ready. Mercur's guest inspector records the POST and returns 204 without forwarding; an Endpoint plus agent is what reaches your laptop.How do you secure a webhook endpoint?
Serve the endpoint over HTTPS and verify the provider's signature (HMAC of the raw body) on every request. Reject mismatches. Treat deliveries as at-least-once and make processing idempotent using the event id so retries do not double-charge or double-provision.What to do next
A webhook is an HTTP callback that pushes event data to your URL when something happens. Use APIs for actions you initiate and webhooks for events you must react to. Verify signatures, answer quickly, and process duplicates safely. When you need to see a real POST, inspect it on a public URL, then forward to localhost once the handler exists.
- How to test webhooks
- Test Stripe webhooks locally · Test GitHub webhooks locally · Create a Telegram bot with webhooks
- Handlers: Next.js · Express · FastAPI · Django
- Failures: Webhook errors
Try the online webhook inspector