In this guide
Webhooks Explained: Receive Data from Payments, GitHub and IoT
Your app just accepted an online payment. How does it know the money actually arrived? Or: someone pushes code to your repository — how does your deployment server find out within seconds? The naive answer is polling: ask the service “anything new?” every few seconds. The professional answer is a webhook: you give the service a URL on your server, and it calls that URL the moment the event happens. This guide explains exactly how webhooks work, how to build a receiver, and how to avoid the security and reliability mistakes beginners make.
The problem webhooks solve
Polling works but scales badly. Checking a payment gateway every 5 seconds means 17,280 requests a day per user — nearly all of them returning “nothing changed”. It wastes your server's time, the provider's time, and on metered connections, money. Worse, polling has inherent latency: if you poll every minute, you learn about events up to a minute late.
A webhook inverts the direction: instead of your app asking the service, the service calls your app. One HTTP request per real event, delivered within seconds. Payment gateways, GitHub, messaging platforms, form builders, and IoT cloud services all use this pattern because it is the only sane way to push events to thousands of users' servers.
What a webhook is, precisely
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.
- Your endpoint, a normal route on your server (e.g.
POST /webhooks/payments) that reads the JSON and acts on it.
There is no special protocol, no SDK required, no persistent connection. If you can write a route that accepts a POST request and parses JSON, you can receive webhooks. That simplicity is why the pattern is everywhere — and why the interesting engineering is all in verification, retries, and idempotency, which this guide covers below.
Anatomy of a webhook event
A typical webhook delivery looks like this (structure varies by provider, but the elements are universal):
{
"event": "payment.captured",
"id": "evt_9f2k41x",
"created_at": "2026-09-23T08:12:44Z",
"data": {
"order_id": "ord_10293",
"amount": 49900,
"currency": "INR",
"status": "captured"
}
}
Plus HTTP headers carrying metadata — most importantly a signature the provider computes over the body using a secret only you two share. Your job: verify that signature before trusting a single byte of the body. Anyone on the internet can POST to your endpoint; the signature is the only thing proving the provider sent it.
Verifying signatures: the one security step that matters
Every serious provider (payment gateways, GitHub, messaging APIs) signs webhook payloads with HMAC-SHA256: they hash the raw request body with your secret and send the hex digest in a header. You recompute the same hash on your side and compare. If it matches, the body is authentic and unmodified. Here is the shape of it in Flask — note the comparison uses a constant-time function so timing attacks cannot leak the signature:
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_replace_with_your_actual_secret"
@app.route("/webhooks/payments", methods=["POST"])
def payments():
raw_body = request.get_data()
received = request.headers.get("X-Signature", "")
expected = hmac.new(WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(received, expected):
abort(401)
event = request.get_json()
handle_event(event)
return "", 200
Three rules that are non-negotiable in anything touching money or access control:
- Never skip verification, even in testing. An unverified endpoint is an open API anyone can invoke — test what an attacker could do with it and you will verify.
- Use HTTPS only. A signature proves authenticity but does not encrypt; without TLS the body travels in cleartext.
- Keep the secret out of your code. Read it from an environment variable. Committing a webhook secret to a public repository is one of the most common student security incidents.
Webhooks vs polling vs WebSockets vs SSE
Students often blur these, so here is the decision rule:
- Polling — your app asks repeatedly. Use when the provider offers no webhooks, events are rare and latency-tolerant, or you control both ends and simplicity wins.
- Webhooks — the provider calls you per event. Use when events originate on the provider's side, you need near-real-time reaction, and your server has a public URL.
- WebSockets — a persistent two-way channel. Use for interactive, bidirectional streams (chat, live dashboards, multiplayer). Overkill for “tell me when X happens”.
- Server-Sent Events (SSE) — a one-way stream from your server to browsers. Use when your own backend pushes updates to your frontend.
Notice the pattern: webhooks push events between organizations' servers; WebSockets and SSE push within your own system's live UI. Different jobs, different tools.
Building a webhook receiver: the reliable pattern
Beyond signature verification, production receivers follow a pattern that handles the real world's messiness:
- Verify the signature (as above). Reject anything else with 401.
- Acknowledge fast. Return HTTP 200 within a few seconds. Providers interpret slow or failed responses as “not delivered” and will retry — if your handler takes 30 seconds to send emails and update databases, you will process the same event twice.
- Queue the real work. Push the event onto a queue (or a background task) and do the heavy processing asynchronously. Fast ACK plus background processing is the entire reliability story.
- Make handlers idempotent. Providers retry deliveries, networks duplicate packets, and your queue might redeliver. Design every handler so processing the same event twice is harmless — check the event ID against a record of processed events before acting.
- Log everything. Store the raw payload of every delivery for at least a few weeks. When a payment goes missing, those logs are your evidence.
Testing webhooks locally
Your laptop has no public URL, so how do you test? Two approaches:
- Send fake events with curl. Craft the JSON yourself and POST it to your local server. This tests your handler logic but not the provider's signature format — compute a valid signature with a tiny script so verification is exercised too.
- Use a tunnel (tools like ngrok expose your localhost on a public URL) and register that URL in the provider's dashboard. Then trigger a real test event — most providers have a “send test webhook” button. Remember tunnels give you a temporary URL; re-register it each session.
Also worth knowing: providers usually offer a dashboard showing recent deliveries, response codes, and retry attempts. When a webhook “stops working”, that delivery log is the first place to look — it tells you whether the provider sent it, what your server returned, and why.
Retries, ordering, and the failure modes to expect
Understand the delivery guarantees before you depend on them:
- At-least-once, not exactly-once. Providers retry failed deliveries (commonly with exponential backoff over hours or days). Your idempotency design from the previous section is what makes this safe.
- No ordering guarantee. Two rapid events can arrive out of order, especially after retries. If order matters (a subscription cancelled then reactivated), use timestamps or sequence numbers in the payload to reconcile.
- Your endpoint must be idempotent under concurrency. A retry can arrive while the first attempt is still processing — a simple “check then insert” can race. Use database-level uniqueness on the event ID.
- Timeouts are short. Most providers give up after 5–30 seconds. Never do slow work synchronously in the handler.
Using webhooks with hardware: ESP32 to your server
Webhooks are not only for SaaS. A common student architecture: an ESP32 reads sensors and POSTs readings to your server's webhook-style endpoint whenever a threshold is crossed (door opened, temperature exceeded). Your server verifies a shared secret, logs the event, and triggers notifications.
A word of comparison: for continuous sensor telemetry, MQTT (publish/subscribe through a broker) is usually the better fit — it handles flaky WiFi, queuing, and many devices gracefully. Reach for webhooks when the event model fits: discrete, important occurrences that deserve an immediate HTTP push, especially when the sender is a cloud service rather than your own device.
FAQ
Do I need a public server to receive webhooks?
Yes — the provider must reach your URL over the internet. For development, a tunnel works; for anything real, deploy to a VPS or platform with a stable public URL and HTTPS.
Can a webhook endpoint also be my normal API?
Technically yes, but keep them separate. Webhook handlers have different needs (signature verification, fast ACK, idempotent processing) than user-facing API routes, and mixing them invites mistakes.
What status code should I return?
Return 2xx quickly for accepted deliveries, 401 for bad signatures, and 4xx for malformed payloads you will never accept (providers generally do not retry 4xx). Reserve 5xx for genuine transient failures you want retried.
How do I handle a provider that does not sign payloads?
Some smaller services do not offer signatures. Mitigations: restrict by IP allowlist if the provider publishes egress IPs, use a long unguessable path segment as a shared secret, and treat the data as untrusted input — never let it directly trigger privileged actions without corroboration.
Webhooks vs the payment gateway's redirect URL?
The redirect (the page the user lands on after paying) is for UX, not truth — users close tabs and networks drop. The webhook is the authoritative record. Always reconcile payment status from the webhook (or a server-side API query), never from what the browser reports.
Limitations
- Your server needs a stable public HTTPS URL — localhost-only projects cannot receive real webhooks without a tunnel.
- Delivery is at-least-once with no ordering guarantee; your handler must be idempotent and order-tolerant.
- Debugging across organizational boundaries is harder: you depend on the provider's dashboard and docs when deliveries fail silently.
- Webhooks suit discrete events, not high-frequency streams — thousands of events per second belong on a message queue or streaming protocol, not HTTP callbacks.