Webhook Testing in Python
Receive webhooks in Flask or FastAPI, verify HMAC signatures, and debug payloads — complete Python walkthrough with real code examples.
TL;DR: Flask and FastAPI both make great webhook receivers. Read the raw request body before parsing for HMAC verification, return 200 quickly, and offload processing to a background task.
Minimal Flask Webhook Receiver
Flask is a natural fit for webhook handlers — it has minimal boilerplate and gives you direct access to request headers and body. Install with pip install flask and create a single route:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
event_type = request.headers.get('X-Event-Type')
payload = request.json
print(f'Event: {event_type}')
print(f'Payload: {payload}')
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=5000)Always return the 200 before doing any slow work. Webhook providers have strict timeout windows (typically 5–30 seconds) and will mark the delivery as failed if you exceed them.
FastAPI Webhook Handler
FastAPI's BackgroundTasks makes it easy to respond immediately and process the event asynchronously in the same process. Install with pip install fastapi uvicorn:
from fastapi import FastAPI, Request, BackgroundTasks
import json
app = FastAPI()
async def process_event(body: bytes):
data = json.loads(body)
# Process here...
@app.post('/webhook')
async def webhook(request: Request, background_tasks: BackgroundTasks):
body = await request.body()
background_tasks.add_task(process_event, body)
return {'received': True}Note that await request.body() returns the raw bytes — which is exactly what you need for HMAC verification before parsing.
HMAC Signature Verification in Python
Most webhook providers sign payloads with HMAC-SHA256. The golden rule: capture the raw body bytes before calling json.loads(). In Flask that means using request.get_data(); in FastAPI use await request.body().
import hmac
import hashlib
import os
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f'sha256={expected}', signature)Always use hmac.compare_digest() instead of ==. The standard equality operator short-circuits on the first mismatched character, leaking timing information that an attacker can exploit to brute-force your secret.
Wiring it into a Flask route:
@app.route('/webhook', methods=['POST'])
def handle_webhook():
raw_body = request.get_data()
signature = request.headers.get('X-Signature-256', '')
secret = os.environ['WEBHOOK_SECRET']
if not verify_signature(raw_body, signature, secret):
return jsonify({'error': 'Invalid signature'}), 401
payload = json.loads(raw_body)
# Process payload...
return jsonify({'received': True}), 200Using Requex During Development
Before you implement your Flask or FastAPI handler, point the webhook provider at a Requex.me endpoint. This lets you capture the exact JSON structure the provider sends — including all headers, the signature header name, and the signature format — without running any Python code.
Once you have a captured payload, replay it locally with curl or Python's requests library:
import requests
payload = {"id": "evt_123", "type": "payment.succeeded", "amount": 4900}
response = requests.post(
'http://localhost:5000/webhook',
json=payload,
headers={'X-Event-Type': 'payment.succeeded'}
)
print(response.status_code, response.json())This workflow means you can iterate on your handler logic without triggering a real event in the provider each time — dramatically speeding up development.
Capture Your First Python Webhook
Get a public endpoint instantly. Inspect headers, raw body, and signatures — no Flask server needed to start.
Open Requex →Related guides
Start Testing Webhooks Now
Generate your unique URL and test webhooks instantly. Free, no signup.
Open Webhook Tester →