Fix Insecure Webhooks in Django
Webhooks are often the weakest link in a Django stack. Developers frequently expose public endpoints that perform sensitive actions—like upgrading subscriptions or shipping orders—without verifying the source. If you aren't validating cryptographic signatures, you're essentially providing an unauthenticated API for attackers to spoof events. Stop trusting the POST body blindly; start enforcing HMAC verification.
The Vulnerable Pattern
@csrf_exempt
def insecure_webhook(request):
# CRITICAL: No authentication or signature verification
data = json.loads(request.body)
user_id = data.get('user_id')
action = data.get('action')
if action == 'payment_received':
# Attacker can trigger this by simply POSTing to the URL
upgrade_user_account(user_id)
return HttpResponse(status=200)</code></pre>
The Secure Implementation
The secure implementation mitigates spoofing by requiring a shared secret known only to the provider and the receiver. 1. We use 'hmac.new' with SHA256 to hash the raw request body against our secret. 2. We use 'hmac.compare_digest' instead of a standard '==' comparison to eliminate side-channel timing attacks that could leak the secret. 3. The '@csrf_exempt' decorator is required for external POSTs, but the HMAC check replaces CSRF as the primary security layer, ensuring only the legitimate provider can trigger the view.
import hmac
import hashlib
from django.conf import settings
from django.http import HttpResponseForbidden, HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def secure_webhook(request):
received_sig = request.headers.get(‘X-Webhook-Signature’)
if not received_sig:
return HttpResponseForbidden(‘Missing signature’)
# Compute HMAC SHA256 using a shared secret
expected_sig = hmac.new(
settings.WEBHOOK_SECRET.encode(),
request.body,
hashlib.sha256
).hexdigest()
# Use compare_digest to prevent timing attacks
if not hmac.compare_digest(expected_sig, received_sig):
return HttpResponseForbidden('Invalid signature')
data = json.loads(request.body)
# Logic only executes if the payload is authentic
process_verified_event(data)
return HttpResponse(status=200)</code></pre>
Your Django API
might be exposed to Insecure Webhooks
74% of Django apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
Free Tier • No Credit Card • Instant Report
Verified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.