Skip to content

Webhooks with Django — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Webhooks with Django. We cover key concepts, practical examples, and best practices to help you master this topic.

Django provides a robust framework for building webhook systems with its ORM, signals, and middleware architecture. This lesson covers creating webhook consumers and providers in Django, handling CSRF protection correctly, and leveraging Django's ecosystem for webhook management.

What You'll Learn

  • Set up Django webhook endpoints with proper CSRF exemption
  • Implement HMAC signature verification in Django views
  • Use Django signals to trigger webhook events
  • Build a webhook subscription model and admin interface

Why It Matters

Django powers many content platforms, SaaS applications, and APIs that need webhook functionality. Django's built-in admin, ORM, and signal system provide excellent building blocks for webhook infrastructure. Understanding Django-specific patterns helps you avoid common pitfalls like CSRF blocking or database performance issues.

Real-World Use

  • Django-based e-commerce platforms use webhooks to notify external inventory systems
  • Content management systems built with Django send webhooks on publish events
  • Django REST Framework APIs integrate webhooks for third-party integrations
  • SaaS platforms use Django signals to trigger webhook deliveries asynchronously

Mermaid Flow

graph TD
    A[External Provider] --> B[Django View]
    B --> C[CSRF Exempt Decorator]
    C --> D[Raw Body Access]
    D --> E[Signature Verification]
    E --> F{Valid Signature?}
    F -->|No| G[Return 401]
    F -->|Yes| H[Parse JSON]
    H --> I[Process Event]
    I --> J[Return 200]
    K[Django Signal] --> L[Webhook Dispatcher]
    L --> M[Lookup Subscriptions]
    M --> N[Queue Delivery]
    N --> O[HTTP POST to Consumer]

Teacher's Corner

Emphasize that CSRF protection must be disabled for webhook endpoints because webhook providers do not have Django CSRF tokens. The @csrf_exempt decorator is essential. Also stress that request.body must be accessed before any Parsing middleware that may consume it. Compare Django's signal-based approach to Express middleware chains.

Code Examples

Example 1: Webhook Consumer View with Signature Verification

import hashlib
import hmac
import json
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

WEBHOOK_SECRET = "whsec_your_secret".encode()

@csrf_exempt
@require_POST
def webhook_consumer(request):
    signature = request.headers.get("X-Signature-256", "")
    payload = request.body

    expected = hmac.new(WEBHOOK_SECRET, payload, hashlib.sha256).hexdigest()
    expected_header = f"sha256={expected}"

    if not hmac.compare_digest(signature, expected_header):
        return JsonResponse({"error": "invalid signature"}, status=401)

    try:
        event = json.loads(payload)
    except json.JSONDecodeError:
        return JsonResponse({"error": "invalid JSON"}, status=400)

    print(f"Processed: {event.get('type')} [{event.get('id')}]")
    return JsonResponse({"status": "received"})

Expected Output: POST with valid signature returns 200 with {"status": "received"}. Invalid signature returns 401.

Example 2: Webhook Provider Using Django Signals

import hashlib
import hmac
import json
import requests
from django.dispatch import Signal
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from .models import WebhookSubscription, Order

webhook_event = Signal()

@receiver(post_save, sender=Order)
def order_webhook_handler(sender, instance, created, **kwargs):
    event_type = "order.created" if created else "order.updated"
    webhook_event.send(
        sender=Order,
        event_type=event_type,
        data={
            "id": str(instance.id),
            "total": float(instance.total),
            "status": instance.status
        }
    )

@receiver(webhook_event)
def dispatch_webhooks(sender, event_type, data, **kwargs):
    subscriptions = WebhookSubscription.objects.filter(
        events__contains=[event_type],
        active=True
    )
    for sub in subscriptions:
        payload = json.dumps({
            "type": event_type,
            "id": f"{event_type}-{data['id']}",
            "data": data
        })
        signature = hmac.new(
            sub.secret.encode(),
            payload.encode(),
            hashlib.sha256
        ).hexdigest()
        try:
            requests.post(
                sub.url,
                data=payload,
                headers={
                    "Content-Type": "application/json",
                    "X-Signature-256": f"sha256={signature}"
                },
                timeout=10
            )
        except requests.RequestException as e:
            print(f"Delivery failed to {sub.url}: {e}")

Expected Output: When an Order is saved, the signal fires and delivers webhook payloads to all matching subscriptions.

Example 3: Django Admin for Webhook Subscription Management

from django.contrib import admin
from .models import WebhookSubscription

class WebhookSubscriptionAdmin(admin.ModelAdmin):
    list_display = ("url", "events_list", "active", "created_at")
    list_filter = ("active", "created_at")
    search_fields = ("url",)
    fieldsets = (
        (None, {
            "fields": ("url", "secret", "active")
        }),
        ("Events", {
            "fields": ("events",),
            "description": "Enter event types as a JSON list, e.g. [\"order.created\", \"order.updated\"]"
        }),
    )

    def events_list(self, obj):
        return ", ".join(obj.events) if obj.events else "All events"
    events_list.short_description = "Subscribed Events"

admin.site.register(WebhookSubscription, WebhookSubscriptionAdmin)

Expected Output: A clean Django admin interface for managing webhook subscriptions with event type filtering and activation toggling.

Common Mistakes

  1. Forgetting to add @csrf_exempt to webhook endpoints, causing 403 Forbidden responses
  2. Accessing request.body after request.POST or request.GET has been accessed, losing raw content
  3. Using request.POST for JSON payloads instead of request.body with json.loads()
  4. Sending webhooks synchronously in signal handlers, blocking the request-response cycle
  5. Not using hmac.compare_digest for signature comparison, leaving timing attack vulnerabilities
  6. Storing webhook secrets in plaintext in the database without encryption
  7. Creating recursive signal loops when webhook processing modifies the same model

Practice Questions

  1. Why must webhook views be decorated with @csrf_exempt?
  2. How would you make webhook delivery asynchronous in Django?
  3. What is the risk of processing webhooks synchronously in a signal handler?
  4. How would you encrypt webhook secrets at rest in the database?
  5. Challenge: Build a Django app that supports webhook subscriptions via REST API, dispatches events asynchronously using Celery, stores delivery logs in a separate model, and provides an admin dashboard for monitoring delivery success rates.
Answer Key 1. Webhook providers do not have a Django CSRF token. Without `@csrf_exempt`, Django's CSRF middleware rejects all POST requests to the view. 2. Use Celery or Django Channels to queue webhook delivery tasks. The signal handler should create an async task instead of calling `requests.post` directly. 3. Synchronous delivery blocks the HTTP response until all webhooks are sent. A slow or failing consumer endpoint delays the entire request. Use async task queues instead. 4. Use Django's encryption utilities (`django.core.signing`) or a field-level encryption library like `django-encrypted-model-fields` to store secrets encrypted at rest. 5. Create a WebhookSubscription model with encrypted secret field, a DeliveryLog model, a ViewSet for subscription CRUD, a Celery task for delivery with retry logic, and a custom admin view showing delivery statistics with charts.

FAQ

Does Django REST Framework change how webhooks work?

DRF adds serializers, authentication classes, and view sets. For webhook consumers, use plain Django views with @csrf_exempt. For provider APIs, DRF is excellent for managing subscriptions.

How do I run webhook delivery tasks asynchronously?

Use Celery with Redis or RabbitMQ as a broker. Create a Celery task that performs the HTTP POST. Call deliver_webhook.delay(sub_id, payload) from your signal handler.

Should I use Django Channels for webhooks?

Django Channels is for WebSockets, not HTTP webhooks. For real-time push, Channels works. For standard webhooks, regular Django views are appropriate.

How do I handle webhook payloads that exceed Django's request size limits?

Set DATA_UPLOAD_MAX_MEMORY_SIZE in settings. For very large payloads, consider streaming or increasing the limit. Providers rarely send payloads over 1MB.

Can I use Django migrations to manage webhook subscription schema changes?

Yes. Use Django migrations for schema changes to your webhook models. Always version your webhook payload format and include a version field.

How do I test webhook views in Django?

Use Django's TestClient with csrf_exempt enabled, or use RequestFactory to construct requests manually. Test signature verification with known test secrets and payloads.

Mini Project

Build a Django webhook manager app. Create models for WebhookSubscription (url, secret, events, active flag), DeliveryLog (subscription, event_id, status_code, response_body, delivered_at). Implement: (1) a consumer view with signature verification, (2) a signal-based dispatcher using Celery, (3) an admin interface with subscription management and delivery log browsing, (4) a management command to retry failed deliveries, and (5) a REST API for subscription CRUD using DRF.

What's Next

Learn how to build webhook systems with Spring Boot, a popular Java framework for enterprise webhook infrastructure.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro