You’ve just built a payment integration. Your app needs to know when a charge succeeds, so you write a loop that checks the payment provider’s API every few seconds. It works — until you’re burning through your API rate limit, your server is doing thousands of pointless requests, and you’re still finding out about failed charges minutes late.
There’s a better way. It’s called a webhook.
The Polling Problem
The approach described above is called polling — your application repeatedly asks an external service “has anything happened yet?” Most of the time the answer is no, and the request was wasted.
Polling has some real downsides:
- Wasted resources. The vast majority of requests return nothing new.
- Latency. You only discover events as fast as your polling interval. Poll every 30 seconds and you could be 29 seconds behind.
- Rate limits. Most APIs cap how many requests you can make. Polling eats into that budget fast.
- Scaling challenges. If you need to monitor thousands of resources, polling becomes expensive quickly.
Polling works in a pinch, but it doesn’t scale well and it’s not how modern integrations are built.
Webhooks: Don’t Call Us, We’ll Call You
A webhook flips the model. Instead of your app asking “did anything happen?”, the external service tells you when something happens by sending an HTTP request to a URL you provide.
Think of it like the difference between refreshing a tracking page every hour versus getting a text message when your package ships.
Here’s the flow:
- You give the external service a URL — your webhook endpoint.
- When an event occurs (a payment succeeds, a user signs up, a file is uploaded), the service sends an HTTP POST request to your endpoint.
- Your endpoint receives the request, processes the data, and responds with a
200 OKto acknowledge receipt.
That’s it. No polling loops, no wasted requests, no artificial delays.
The Anatomy of a Webhook Request
A webhook is just a standard HTTP POST request. There’s nothing exotic about the protocol — it’s the same HTTP your browser uses every day. Here’s what a typical webhook request looks like:
POST /webhooks/payments HTTP/1.1
Host: api.yourapp.com
Content-Type: application/json
X-Webhook-Signature: sha256=a1b2c3d4e5f6...
{
"event": "payment.completed",
"timestamp": "2026-03-19T14:22:00Z",
"data": {
"payment_id": "pay_8xk2m9",
"amount": 4999,
"currency": "usd",
"customer_id": "cust_3jd82k"
}
}
Let’s break down what’s happening:
- Method and path: It’s a
POSTto whatever URL you registered. The path is yours to define. - Content-Type: Almost always
application/json, though some services useapplication/x-www-form-urlencoded. - Signature header: Many providers include an HMAC signature so you can verify the request is authentic and hasn’t been tampered with. The header name varies by provider.
- Body: A JSON payload describing the event — what happened, when, and the relevant data.
Building a Simple Webhook Endpoint
A webhook endpoint is just an HTTP route that accepts POST requests. Here’s a minimal example in a few popular languages:
Node.js (Express)
app.post("/webhooks/payments", (req, res) => {
const event = req.body;
console.log(`Received event: ${event.event}`);
// Process the event
switch (event.event) {
case "payment.completed":
// Update order status, send receipt, etc.
break;
case "payment.failed":
// Notify customer, retry logic, etc.
break;
}
res.status(200).send("OK");
});
Python (Flask)
@app.route("/webhooks/payments", methods=["POST"])
def handle_webhook():
event = request.get_json()
print(f"Received event: {event['event']}")
if event["event"] == "payment.completed":
# Update order status, send receipt, etc.
pass
elif event["event"] == "payment.failed":
# Notify customer, retry logic, etc.
pass
return "OK", 200
Go
func handleWebhook(w http.ResponseWriter, r *http.Request) {
var event WebhookEvent
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
log.Printf("Received event: %s", event.Event)
switch event.Event {
case "payment.completed":
// Update order status, send receipt, etc.
case "payment.failed":
// Notify customer, retry logic, etc.
}
w.WriteHeader(http.StatusOK)
}
The key pattern across all of these: accept the request, do your work, and return a 200 status code quickly. The 200 response tells the sender “I got it” — without that acknowledgment, most providers will assume delivery failed and retry.
When to Use Webhooks
Webhooks are a great fit when:
- You need to react to events in near real-time. Payment confirmations, user actions, CI/CD triggers, and status changes are all natural webhook use cases.
- The events are infrequent or unpredictable. If a customer might update their billing info once a month, polling for that change every minute is wasteful. A webhook delivers the update the moment it happens.
- You’re integrating with third-party services. Stripe, GitHub, Twilio, Shopify, Slack — most modern APIs offer webhooks because they’re the most efficient way to keep your system in sync.
Webhooks might not be the best fit when:
- You need to pull large datasets. Webhooks notify you about individual events. If you need to sync thousands of records, a batch API or data export is usually better.
- The consumer is offline or can’t receive HTTP requests. Webhooks require a publicly accessible endpoint. If your consumer runs behind a firewall or only runs periodically, a message queue or polling approach might be more practical.
Webhooks vs. Other Integration Patterns
| Pattern | How it works | Best for |
|---|---|---|
| Polling | Consumer repeatedly requests updates | Simple integrations, no public endpoint available |
| Webhooks | Producer pushes events via HTTP | Real-time notifications, third-party integrations |
| WebSockets | Persistent bidirectional connection | Live UI updates, chat, streaming data |
| Message Queues | Events routed through a broker (SQS, Kafka) | High-throughput internal systems, guaranteed ordering |
Webhooks occupy a sweet spot: they’re real-time, simple to implement, and use standard HTTP — no special infrastructure required on either side.
What Can Go Wrong
Webhooks are simple in concept but there are a few things that can trip you up in production:
- Your endpoint is down. If your server is unreachable when the webhook fires, the event is lost — unless the sender retries.
- Duplicate deliveries. Retries mean you might receive the same event more than once. Your code needs to handle that gracefully.
- Security. Without signature verification, anyone who discovers your webhook URL can send fake events to your endpoint.
- Timeouts. If your endpoint takes too long to respond, the sender may treat it as a failure and retry, leading to duplicate processing.
- Ordering. Events don’t always arrive in the order they occurred, especially when retries are involved.
These aren’t reasons to avoid webhooks — they’re reasons to implement them thoughtfully. We’ll dive deeper into each of these challenges in upcoming posts.
Getting Started
If you’re new to webhooks, here’s a simple path to get started:
- Pick a service you already use that offers webhooks (Stripe, GitHub, etc.).
- Set up a simple endpoint using one of the code examples above.
- Use a tool like ngrok to expose your local server to the internet for testing.
- Register your URL with the service and trigger a test event.
- Verify it works by checking your server logs for the incoming request.
From there, you’ll want to add signature verification, idempotent processing, and proper error handling — topics we’ll cover in detail in this series.
Wrapping Up
Webhooks are one of the most practical tools in a developer’s integration toolkit. They’re simple, efficient, and built on HTTP standards you already know. Once you understand the basic pattern — register a URL, receive events, respond with a 200 — you can integrate with nearly any modern service.
But as your webhook usage grows, the operational challenges grow with it. Reliable delivery, retry management, signature verification, and observability all become critical. That’s exactly the kind of problem Hookbridge was built to solve — but more on that in future posts.
Next up: Why Webhook Delivery Is Harder Than You Think — where we’ll dig into the real-world failure modes that make webhook delivery a genuinely hard problem.