SafeWebhook/ Guides
Launch Workbench
Django / Django REST FrameworkPython

Django Webhook Guide: @csrf_exempt & HMAC Signature Verification

Learn how to build secure webhook receivers in Django. Exempt webhook routes from CSRF protection and access `request.body` for HMAC validation.

Direct Answer / Quick Implementation Guide

In Django, apply the `@csrf_exempt` decorator to your webhook view function to prevent 403 Forbidden CSRF errors. Access `request.body` (raw bytes) to compute HMAC-SHA256 signatures before parsing with `json.loads()`.

Essential Implementation Takeaways

  • Always add `@csrf_exempt` to webhook views.
  • Use `request.body` for raw payload bytes.
  • Validate signatures with `hmac.compare_digest()`.
  • Use Celery tasks to process heavy webhook workflows asynchronously.

Common Gotchas in Django / Django REST Framework & How to Fix Them

HTTP 403 Forbidden CSRF error
Fix:Add `@csrf_exempt` decorator above the view function.

Complete Production Boilerplate

Python
# views.py (Django Webhook View)
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse, HttpResponseBadRequest, HttpResponseForbidden
import hmac
import hashlib
import json
import os

WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "secret").encode('utf-8')

@csrf_exempt
def webhook_receiver(request):
    if request.method != 'POST':
        return HttpResponseBadRequest("Only POST method allowed")

    raw_body = request.body
    signature = request.headers.get('X-Signature')

    if not signature:
        return HttpResponseBadRequest("Missing signature")

    computed = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(signature, computed):
        return HttpResponseForbidden("Invalid signature")

    data = json.loads(raw_body.decode('utf-8'))
    # Trigger Celery task: process_event.delay(data)

    return JsonResponse({"status": "received"}, status=200)

Step-by-Step Setup Guide

1
Import `@csrf_exempt` from `django.views.decorators.csrf`.
2
Decorate your view with `@csrf_exempt`.
3
Read raw bytes from `request.body`.
4
Verify signature and return `JsonResponse({"status": "received"}, status=200)`.

How to Test Locally on Your Machine

  • Run `python manage.py runserver 8000`.
  • Forward webhooks from SafeWebhook to `http://localhost:8000/api/webhook/`.

Frequently Asked Questions: Django / Django REST Framework Webhooks

Why does Django require @csrf_exempt on webhook views?

Django enforces CSRF token validation on all POST requests by default. Webhooks originate from external servers without session cookies, so they must be exempted.

Test Your Django / Django REST Framework Webhook Handler Live

Capture payloads, simulate errors, and replay directly to your localhost server.

Launch Webhook Workbench →

Explore Other Webhook Framework Guides