Webhook monitoring for Django.
Middleware that wraps every webhook view — captures latency, status, and signature validity without touching your view logic. Under 3ms of overhead per request.
Add the package
pip install outworx-hooks
# or
poetry add outworx-hooks
# or
uv add outworx-hooksSet environment variables
Grab your API key from the dashboard and load it through your usual settings mechanism (django-environ,.env, or a secrets manager).
# .env
OUTWORX_HOOKS_API_KEY=ow_live_...
STRIPE_WEBHOOK_SECRET=whsec_...Add to settings.py
Add OutworxHooksMiddleware to your MIDDLEWARE list and define OUTWORX_HOOKS_OPTIONS. The signature is verified before your view runs, and duplicate retries (same event ID within 24h) short-circuit with the cached response.
# settings.py
import os
from outworx_hooks import init
init(api_key=os.environ["OUTWORX_HOOKS_API_KEY"])
MIDDLEWARE = [
# ... your existing middleware
"outworx_hooks.integrations.django.OutworxHooksMiddleware",
]
OUTWORX_HOOKS_OPTIONS = {
"provider": "stripe",
"signature_secret": os.environ["STRIPE_WEBHOOK_SECRET"],
# Dedupe on the Stripe event ID — retries return the cached response.
"idempotency_key": lambda req, body, headers: body.get("id") if body else None,
}Then your view is unchanged:
# views.py
import json
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
@csrf_exempt
@require_POST
def stripe_webhook(request):
# Signature has been verified by the middleware; duplicates filtered.
body = json.loads(request.body)
return JsonResponse({"received": True})Per-view configuration
When one app handles webhooks from multiple providers, the @with_webhook_monitoring decorator gives you per-view control. Built-in HMAC-SHA256 verification covers Stripe, GitHub, Shopify, Svix / Clerk, and Slack.
# Per-route override — when one app handles multiple providers.
from outworx_hooks.integrations.django import with_webhook_monitoring
from outworx_hooks import TrackOptions
@csrf_exempt
@require_POST
@with_webhook_monitoring(TrackOptions(
provider="github",
signature_secret=os.environ["GITHUB_WEBHOOK_SECRET"],
idempotency_key=lambda req, body, headers: headers.get("x-github-delivery"),
))
def github_webhook(request):
payload = json.loads(request.body)
# ... handle GitHub event
return JsonResponse({"ok": True})Other Python frameworks
Using FastAPI or Flask? Each has its own integration pattern — same SDK, same dashboard. The full TrackOptions reference lives in the main SDK docs.
Catch silent drops
When your view returns 200 but never actually processes the event, everything looks fine — until state is wrong. Set require_processing_mark=True in settings and call request.outworx_track.processed() when your business logic finishes:
# settings.py
OUTWORX_HOOKS_OPTIONS = {
"provider": "stripe",
"signature_secret": os.environ["STRIPE_WEBHOOK_SECRET"],
"require_processing_mark": True, # ← opt in
}
# views.py
def stripe_webhook(request):
body = json.loads(request.body)
charge_customer(body)
request.outworx_track.processed() # ← explicit ack
return JsonResponse({"received": True})Full guide: Silent drop detection.