Webhooks
Normal APIs: you call them when you want something. Webhooks flip it: they call you when something happens. "Don't call us, we'll call you" — as an architecture.
The idea
You give the provider a URL: "when a payment completes, POST the details here." They do. Your server receives an ordinary HTTP POST — a JSON body describing the event — and acts on it. No polling ("are we there yet?" every 30 seconds), no wasted requests.
| Polling | Webhooks |
|---|---|
| You ask repeatedly | They tell you once |
| Latency up to your poll interval | Near-instant |
| Wasted requests when nothing happened | Traffic only on events |
| Simple to implement | You run a reachable server |
Stripe, GitHub, and nearly every payment/CI/chat platform offer webhooks for exactly this reason.
Receiving one (it's just a POST endpoint)
def do_POST(self):
if self.path == "/webhook":
length = int(self.headers.get("Content-Length", 0))
event = json.loads(self.rfile.read(length))
print("Event:", event["type"]) # e.g. "payment.completed"
self._send(200, {"received": True})
That's it — a webhook receiver is a POST endpoint that reads JSON and answers 200 quickly. (Quickly matters: the sender times out and retries, so do slow work — emails, database writes — after responding, or in a background job.)
Trust, but verify
Anyone can POST to your URL — including attackers faking events ("payment.completed" for an order nobody paid for). Providers sign their webhooks:
- You and the provider share a secret.
- They send the payload plus a signature header
(
X-Signature: sha256=abc...) — an HMAC of the body with the secret. - You recompute the HMAC and compare. Match = genuine.
Never act on an unsigned webhook. And never trust fields the sender could forge — after a "payment.completed" event, fetch the payment's status from the API yourself before shipping the goods. The webhook is a notification; the API is the truth.
The operational realities
- Retries: senders retry failed deliveries (non-2xx or timeouts) — your handler must be idempotent (processing the same event twice is harmless). Track event ids you've seen.
- Ordering isn't guaranteed. "Created" can arrive after "updated." Design for out-of-order events.
- Your URL must be reachable — localhost won't cut it for real providers (tunnels like ngrok work for testing).
Next: Troubleshooting