What Is a Webhook? How Webhooks Work, With Examples
01Introduction
A webhook is an HTTP request that one app sends to another app's URL as soon as something happens. Instead of you asking "anything new?" over and over, the other app tells you. A payment succeeds, a customer signs up, code is pushed, and the app sends the details to a URL you gave it.
Think of a doorbell. Without one, you'd keep opening the door to check if someone's there. With one, you go to the door when it rings. A webhook is the doorbell.
This guide covers how webhooks work, how they differ from APIs and polling, and real examples. It also shows how to receive one safely, with code we tested, and how to test your webhook handler without waiting for real events.
02A webhook example
Say you want to know every time someone opens an issue in your GitHub repository. You give GitHub a URL, such as https://yourapp.com/webhooks/github. When an issue is opened, GitHub sends a POST request to that URL:
POST /webhooks/github HTTP/1.1
Content-Type: application/json
X-GitHub-Event: issues
X-GitHub-Delivery: 72d3162e-cc78-11e3-81ab-4c9367dc0958
X-Hub-Signature-256: sha256=7d38cdd689735b008b3c702edd92eea23791c5f6...
{
"action": "opened",
"issue": { "number": 42, "title": "Login button does nothing" }
}
Your app reads it and does something: posts to Slack, creates a ticket, sends an email. GitHub didn't wait to be asked.
03How webhooks work
- You create an endpoint. A URL in your app that accepts POST requests. (New to the term? See What is an API endpoint?)
- You register it. In the other app's settings, you paste the URL and pick which events you want.
- An event happens. The other app sends an HTTP request to your URL, usually with a JSON body describing the event.
- You respond. Your app returns a
2xxstatus to say "got it". If it doesn't, most providers try again later.
04Webhook vs API
A webhook is an API call in reverse. With a regular API call, your app asks and the other app answers. With a webhook, the other app sends and your app listens.
| API call | Webhook | |
|---|---|---|
| Who starts it | Your app | The other app |
| When | Whenever you ask | When an event happens |
| Direction | You pull data | Data is pushed to you |
| You need | The other app's endpoint and a key | Your own public endpoint |
| Good for | Getting data on demand, taking actions | Reacting to events as they happen |
Most integrations use both. A webhook tells you something happened, and an API call fetches more details or takes the next step.
05Webhooks vs polling
The alternative to a webhook is polling: calling an API again and again until the answer changes.
Polling is simpler, and some APIs only offer polling. ApyHub's long-running jobs work this way today. We submitted a translation job to the Text Translation API and got a job ID back:
{ "job_id": "1dff777f-b5e3-4d5d-873c-4543402296b4" }The first status check returned "status": "new". A few seconds later, the second returned the result:
{
"data": {
"attributes": {
"status": "success",
"result": { "content": "Ihre Bestellung wurde versandt.", "to_language": "German" }
}
}
}That's two calls for one result. For a job that finishes in seconds, that's fine. For events that might happen once a day, polling wastes calls and still finds out late.
| Polling | Webhook | |
|---|---|---|
| How fast you find out | When you next check | Within seconds |
| Wasted calls | Many, most return "no change" | None |
| Setup | Only API calls | A public endpoint you have to secure |
| Works behind a firewall | Yes | Needs a URL the sender can reach |
Rule of thumb: use webhooks for events you can't predict (payments, sign-ups, pushes). Use polling for jobs you started yourself and expect to finish soon.
06Common webhook examples
- Payments: Stripe sends
payment_intent.succeededwhen a payment goes through, so you can fulfill the order. - Code: GitHub sends
pushandpull_requestevents to trigger CI or deploys. - E-commerce: Shopify sends order and refund events to update stock and accounting.
- Forms and sign-ups: form tools send each new submission to your CRM or your own API.
- Automation tools: Zapier, Make and n8n start workflows from incoming webhooks.
Incoming vs outgoing webhooks
Slack and Discord use the word the other way round. Their "incoming webhooks" are URLs that you send messages to, and the message appears in a channel. Your app is the sender, and Slack or Discord receives. It's the same mechanism, an HTTP POST to a URL, with the roles swapped.
07How to receive a webhook safely
Your webhook URL is public, so anyone can send requests to it. Here's a minimal receiver in Python (Flask) for GitHub. We tested it with a valid signature, a repeated delivery and a forged signature, and got 200, 200 and 401:
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["WEBHOOK_SECRET"].encode()
seen = set() # use a database in production
@app.post("/webhooks/github")
def github_webhook():
sig = request.headers.get("X-Hub-Signature-256", "")
expected = "sha256=" + hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
delivery = request.headers.get("X-GitHub-Delivery")
if delivery in seen:
return "", 200 # already handled, stop the retries
seen.add(delivery)
event = request.get_json()
print("new event:", request.headers.get("X-GitHub-Event"), event.get("action"))
return "", 200The five rules it follows:
- Verify the signature. GitHub signs every delivery with HMAC-SHA256 in the
X-Hub-Signature-256header, and says to compare it in constant time, never with a plain==(GitHub Docs). Stripe uses theStripe-Signatureheader the same way (Stripe Docs). - Use the raw body. Check the signature against the exact bytes you received. Many frameworks parse or reformat the JSON first, which breaks the check.
- Return a
2xxfast. Stripe says to respond before any complex logic, and to process events through a queue. Slow responses time out and count as failures. - Expect duplicates. Providers retry failed deliveries, Stripe for up to three days, and the same event can arrive more than once. Store event IDs and skip ones you've handled.
- Use HTTPS. Stripe requires an HTTPS endpoint in live mode.
The idea behind rule 4 is called idempotency. See What is an idempotent request?
08How to test a webhook
Waiting for a real payment or a real push every time you change your handler is slow. The faster way is to send the webhook yourself: the same headers, the same body, the same signature, to your local endpoint.
Voiden, our free, open-source API client, works well for this:
- Save a real delivery as a request. Copy a payload from the provider's delivery log (GitHub and Stripe both show them) into a request file, with its headers.
- Sign it. A pre-request script computes the HMAC signature from your test secret, so your handler sees a correctly signed delivery.
- Send it to localhost. Point it at
http://localhost:5000/webhooks/githuband check you get200. - Test the failure cases. Duplicate the request with a wrong signature (expect
401) and send the same delivery twice (expect200and no second side effect). These are the cases that break in production. - Commit the files. Voiden stores requests as Markdown files in your repo, so your teammates and CI replay the same webhook tests, and an AI agent can run them too.
This tests your receiver, the side you own. To check a provider can reach you, use its own test tools as well, such as stripe listen and stripe trigger in the Stripe CLI, or GitHub's "Redeliver" button.
09When a webhook arrives, call an API
A webhook usually starts a chain of work. A form tool sends a new sign-up, and before saving it, your handler checks the address with the Email Validation API. A store sends an order in another language, and your handler translates the note with the Text Translation API. For longer chains, see API chaining.
AI agents work the same way. Through ApyHub MCP, every endpoint in the ApyHub catalog is available as a ready-made tool. An agent triggered by a webhook can validate, translate or enrich the data with Claude, ChatGPT or Cursor calling tested endpoints, which gives consistent results and uses fewer tokens than writing the code each time.
Browse ready-made APIs to call from your webhooks →
10Conclusion
A webhook is an app sending an HTTP request to your URL when something happens, so you don't have to keep asking. Use webhooks for events you can't predict, and polling for jobs you started yourself. When you receive webhooks, verify the signature, respond fast, and expect the same event more than once. Test all three by replaying signed deliveries from Voiden before real events reach production.
Start free on ApyHub, no card required →
11FAQ
What is a webhook in simple terms?
A message one app sends to another app's URL when something happens, such as a payment or a new sign-up.
What is the difference between a webhook and an API?
With an API, your app asks for data. With a webhook, the other app sends data to you when an event happens. A webhook is an API call in reverse.
Can you give me an example of a webhook?
When a customer pays, Stripe sends a payment_intent.succeeded event to your URL so your app can fulfill the order.
How do I create a webhook?
Build an endpoint in your app that accepts POST requests, deploy it at a public HTTPS URL, then paste that URL into the other app's webhook settings and choose the events you want.
Are webhooks free?
Usually, yes. Most platforms include webhooks at no extra cost. You pay for hosting the endpoint that receives them.
Are webhooks secure?
They can be. Verify the signature on every request, use HTTPS, and keep the signing secret out of your code.
What happens if my webhook endpoint is down?
Most providers retry. Stripe retries undelivered events for up to three days. Make your handler skip events it has already processed.
How can I test a webhook?
Send it yourself. Save a real delivery, with its headers and body, in Voiden, sign it with a pre-request script, and replay it against your local endpoint as often as you need. Add a copy with a wrong signature and a repeated delivery to test the failure cases. To check the provider can reach you, use its tools too, such as the Stripe CLI.
12About ApyHub
ApyHub runs a catalog of ready-made APIs and is the trusted operational layer between your application and the services it depends on. One subscription, billed in atoms, covers the whole catalog of over 1,500 endpoints and capabilities, and it keeps growing. Every service is evaluated before it is listed and carries machine-readable certification covering GDPR, SOC 2 and ISO 27001. Every endpoint is MCP-ready by default. ApyHub runs dedicated EU and US regions and serves more than 65,000 developer workspaces every month. Start on the free tier with no card, or publish your own API on ApyHub.
13Sources
- GitHub Docs, Validating webhook deliveries:
X-Hub-Signature-256, HMAC-SHA256, constant-time comparison - Stripe Docs, Manage webhook endpoints: retries for up to three days, fast
2xxresponses, duplicate events, HTTPS in live mode,Stripe-Signature - ApyHub, Text Translation API: job submitted and polled November 5, 2026
