Webhooks Explained: How Services Talk to Each Other

Webhooks flip the usual client-polls-server model on its head — instead of repeatedly asking “has anything changed yet?”, the server proactively pushes a notification the moment something actually happens.

from flask import Flask, request
import hmac, hashlib

app = Flask(__name__)

@app.route('/webhooks/payment', methods=['POST'])
def handle_payment_webhook():
    # ALWAYS verify the signature - anyone can POST to a public endpoint otherwise
    signature = request.headers.get('X-Signature')
    expected = hmac.new(WEBHOOK_SECRET.encode(), request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected):
        return "Invalid signature", 401

    event = request.json
    if event['type'] == 'payment.succeeded':
        mark_order_paid(event['data']['order_id'])

    return "", 200  # respond quickly - do heavy processing asynchronously, not inline

Signature Verification Is Not Optional

Without verifying the signature, any attacker who discovers your webhook URL can POST fake events — a fake “payment succeeded” webhook could mark an unpaid order as paid. Every reputable webhook provider (Stripe, GitHub, Twilio) signs its payloads with a shared secret specifically so you can verify authenticity before acting on the event.

Respond Fast, Process Slow

Webhook senders typically retry if they don’t get a fast response (often within a few seconds) — doing heavy processing inline inside the webhook handler risks timeouts and duplicate deliveries. Acknowledge receipt immediately, then hand the actual work off to a background queue.