Webhooks Explained: How They Work and Why They Matter
Learn what webhooks are, how they replace inefficient polling with real-time notifications, and how to build a secure webhook endpoint in Python with Flask.
Webhooks are the quiet workhorses of modern software. While APIs are the clerks you ask for data, webhooks are the messengers that knock on your door the second something happens. If you've ever seen a Slack notification pop up the moment a payment clears, or a GitHub action trigger the second someone pushes code, you've watched webhooks do their thing.
But how do they actually work? And why are they suddenly everywhere? Let's break it down without the jargon.
The Polling Problem
Before webhooks became mainstream, if you wanted to know whether something changed, you had to ask. Constantly. Your system would ping an API every few seconds or minutes, hoping to catch a change. That's called polling, and it's wasteful.
Imagine calling a restaurant every 30 seconds to ask if your table is ready. Frustrating for you, annoying for the staff, and a huge waste of everyone's time. That's exactly what old-school integrations felt like.
Polling works for some things, but it's slow, inefficient, and creates unnecessary load on servers. You're asking "is it done yet?" when you could just wait for a call.
Enter the Webhook
A webhook flips the script. Instead of you asking, the server tells you. It's a reverse API — the provider sends an HTTP POST request to a URL you specify, the moment an event occurs.
Here's the simplest real-world example:
You set up a webhook URL on your server, like https://yoursite.com/webhook/payment. You register that URL with Stripe. Now, every time a customer pays, Stripe sends a JSON payload to that endpoint instantly. No polling. No delays. Just a direct, real-time notification.
The whole flow is:
- You register a callback URL with the service.
- Something happens (a payment, a new subscriber, a file upload).
- The service sends an HTTP request to your URL with the event details.
- Your server processes the data and does whatever needs doing.
That's it. The magic is in the timing. Instead of checking every minute, you're reacting within milliseconds.
The Anatomy of a Webhook Payload
Webhooks aren't a standard like REST, but most follow a similar pattern. The payload is usually JSON, and it contains three key things:
- Event name – What happened? (
payment.succeeded,user.created,repo.pushed) - Timestamp – When did it happen?
- Data – The actual details (customer ID, amount, commit hash, etc.)
Here's a stripped-down example of what Stripe sends when a charge succeeds:
{
"id": "evt_1J2abc123",
"event": "charge.succeeded",
"data": {
"amount": 4999,
"currency": "usd",
"customer": "cus_Qwerty123",
"receipt_email": "buyer@example.com"
},
"created": 1712345678
}
Your webhook endpoint receives this, validates it, and does the next step — maybe updating your database, sending a thank-you email, or triggering a fulfillment order.
Why Webhooks Are So Powerful
The big win is real-time efficiency. You get data the instant it exists, not the moment you remember to check. That opens doors to:
- Live dashboards – Update a customer's order status instantly instead of on a 60-second refresh.
- Event-driven automation – When a new user signs up, automatically create their account, send a welcome email, and log the activity — all without human input.
- Reduced server load – Your systems only work when there's actual work, not on a constant polling schedule.
For example, at PythonSkillset, if you ran a subscription service, you wouldn't want your billing system chasing every customer's payment status. You'd let Stripe call your webhook the moment a payment fails, and then you'd send a "Hey, your card was declined" email — instantly.
The Hidden Complexity: Reliability
Webhooks are great, but they're not a fire-and-forget system. If your server is down when the webhook fires, the event could be lost forever. That's why reliable implementations need:
- Retry logic – Most providers retry failed deliveries (often with exponential backoff).
- Logging – Every incoming webhook should be logged for debugging.
- Idempotency – If a webhook is delivered twice, your system shouldn't double-process it. You check the event ID and skip if you've already handled it.
The painful truth is that webhooks fail. Networks drop, timeouts happen, servers crash. A mature webhook consumer handles these edge cases gracefully — not just the happy path.
Building a Webhook Endpoint in Python
Let's make this real with code. Here's a minimal Flask app that receives Stripe webhooks:
from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
STRIPE_SECRET = b'whsec_your_webhook_secret'
@app.route('/webhook/payment', methods=['POST'])
def handle_payment():
payload = request.get_data()
sig_header = request.headers.get('Stripe-Signature')
# Verify the signature (don't skip this in production!)
try:
expected = hmac.new(STRIPE_SECRET, payload, hashlib.sha256).hexdigest()
# Compare with sig_header (simplified — Stripe uses a timestamp scheme)
if not hmac.compare_digest(sig_header, expected):
return jsonify({'error': 'Invalid signature'}), 400
except Exception:
return jsonify({'error': 'Verification failed'}), 400
event = request.json
event_type = event['type']
print(f"Received event: {event_type}")
if event_type == 'payment_intent.succeeded':
# Do your post-payment logic here
pass
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=5000)
Notice the signature verification — this isn't optional. Anyone can hit your URL with fake events. Without checking the HMAC signature, you'd be trusting unverified data, which is a security hole the size of a barn door.
Real-World Use Cases That Make Sense
Webhooks shine when speed matters:
- Payment processing – Update order status instantly when money moves.
- CI/CD pipelines – When a developer pushes code, automatically start builds and tests.
- Chat integrations – Slack, Discord, and Teams all support incoming webhooks so services can push notifications directly into channels.
- IoT devices – A sensor detects movement and sends a webhook to your alarm system.
Each of these has the same theme: something important just happened, and you need to react now.
When Webhooks Aren't the Answer
Webhooks aren't perfect. If you need a complete snapshot of current state (like "list all my customers"), you still use a regular API. Webhooks are for events, not state. Also, if you operate behind a strict firewall that blocks inbound connections, receiving webhooks becomes a problem — you'd need to use a service like a webhook relay or tunnel.
And remember, webhooks are push-based. If you don't process the data fast enough, events queue up. That's fine for low volume, but you might need a job queue for heavy traffic.
Final Thoughts
Webhooks quietly power the real-time web. They're simple to understand but require careful implementation to do right. The core lesson is this: stop asking, start listening. When you design your next integration, ask yourself — does this need to be real-time? If yes, a webhook is probably your answer.
Once you've built one webhook endpoint, you'll see the pattern everywhere. And you'll never want to go back to polling again.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.