McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Handle Paystack Webhooks in Django REST Framework
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Handle Paystack Webhooks in Django REST Framework

To handle Paystack webhooks in DRF, create an APIView with authentication_classes = [] and permission_classes = [AllowAny]. Read request.body for the raw bytes, compute the HMAC SHA-512 hash, compare it to the x-paystack-signature header, and process the event. Return Response(status=200).

In this article

  1. 1. Webhook APIView
  2. 2. URL Configuration
  3. 3. Log Webhook Events
  4. 4. Async Processing with Celery
  5. 5. Testing
  6. 6. Ship Payments Faster

Webhook APIView

# payments/views.py
import json
import hmac
import hashlib
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny

class PaystackWebhookView(APIView):
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        # 1. Get signature
        signature = request.META.get('HTTP_X_PAYSTACK_SIGNATURE', '')

        if not signature:
            return Response({'error': 'Missing signature'}, status=400)

        # 2. Compute hash
        secret = settings.PAYSTACK_SECRET_KEY.encode('utf-8')
        computed_hash = hmac.new(
            secret,
            msg=request.body,
            digestmod=hashlib.sha512,
        ).hexdigest()

        # 3. Compare
        if not hmac.compare_digest(computed_hash, signature):
            return Response({'error': 'Invalid signature'}, status=400)

        # 4. Process event
        event = json.loads(request.body)
        self.process_event(event)

        return Response(status=200)

    def process_event(self, event):
        from .services import verify_and_fulfill_order

        event_type = event.get('event')
        data = event.get('data', {})

        if event_type == 'charge.success':
            reference = data.get('reference')
            if reference:
                verify_and_fulfill_order(reference)

        elif event_type == 'subscription.create':
            self.handle_subscription_create(data)

        elif event_type == 'invoice.payment_failed':
            self.handle_invoice_failed(data)

    def handle_subscription_create(self, data):
        pass  # Implement based on your subscription model

    def handle_invoice_failed(self, data):
        pass  # Implement based on your subscription model

URL Configuration

# payments/urls.py
from django.urls import path
from .views import InitializePaymentView, VerifyPaymentView, PaystackWebhookView

urlpatterns = [
    path('initialize/', InitializePaymentView.as_view()),
    path('verify/', VerifyPaymentView.as_view()),
    path('webhooks/paystack/', PaystackWebhookView.as_view()),
]

Set the webhook URL in your Paystack dashboard: https://yourdomain.com/api/payments/webhooks/paystack/

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Log Webhook Events

# payments/models.py
class WebhookEvent(models.Model):
    event_type = models.CharField(max_length=100)
    reference = models.CharField(max_length=100, null=True, blank=True)
    payload = models.JSONField()
    processed = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']

Save every event before processing:

def process_event(self, event):
    from .models import WebhookEvent

    WebhookEvent.objects.create(
        event_type=event.get('event', ''),
        reference=event.get('data', {}).get('reference', ''),
        payload=event,
    )

    # ... rest of processing

This gives you an audit trail and makes it easy to debug missing payments.

Async Processing with Celery

# payments/tasks.py
from celery import shared_task
from .services import verify_and_fulfill_order

@shared_task
def process_webhook_event(event):
    event_type = event.get('event')
    data = event.get('data', {})

    if event_type == 'charge.success':
        reference = data.get('reference')
        if reference:
            verify_and_fulfill_order(reference)

# In the webhook view:
def process_event(self, event):
    from .tasks import process_webhook_event
    process_webhook_event.delay(event)

This ensures Paystack gets a 200 within milliseconds while your processing happens in a background worker.

Testing

# payments/tests.py
import json
import hmac
import hashlib
from django.conf import settings
from rest_framework.test import APITestCase

class WebhookTest(APITestCase):
    def test_valid_webhook(self):
        payload = json.dumps({
            'event': 'charge.success',
            'data': {'reference': 'test_123', 'amount': 500000, 'status': 'success'},
        })

        signature = hmac.new(
            settings.PAYSTACK_SECRET_KEY.encode('utf-8'),
            msg=payload.encode('utf-8'),
            digestmod=hashlib.sha512,
        ).hexdigest()

        response = self.client.post(
            '/api/payments/webhooks/paystack/',
            data=payload,
            content_type='application/json',
            HTTP_X_PAYSTACK_SIGNATURE=signature,
        )

        self.assertEqual(response.status_code, 200)

    def test_invalid_signature(self):
        response = self.client.post(
            '/api/payments/webhooks/paystack/',
            data='{}',
            content_type='application/json',
            HTTP_X_PAYSTACK_SIGNATURE='invalid',
        )

        self.assertEqual(response.status_code, 400)

Ship Payments Faster

This guide is part of the Paystack integration guides by language and framework series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Set authentication_classes = [] and permission_classes = [AllowAny] on the webhook APIView. Paystack cannot authenticate with your API.
  • ✓DRF does not parse request.body automatically for non-form content types. request.body gives you the raw bytes needed for HMAC.
  • ✓Use hmac.compare_digest for constant-time comparison to prevent timing attacks.
  • ✓Return Response(status=200) immediately. Process events asynchronously with Celery if needed.
  • ✓Make handlers idempotent. Paystack retries failed deliveries and may send the same event multiple times.
  • ✓Log every webhook event in a database table for debugging and audit purposes.

Frequently Asked Questions

Do I need @csrf_exempt with DRF APIView?
No. DRF APIViews are already CSRF-exempt by default because they use session authentication or token authentication, not Django form-based CSRF. Setting authentication_classes = [] is sufficient.
Why not use DRF parsers for the webhook body?
DRF JSONParser would parse the body correctly, but you need the raw bytes for HMAC verification. request.body gives you the raw bytes even when DRF also parses the JSON into request.data.
Can I use DRF permissions to verify the webhook signature?
You could create a custom permission class that checks the signature, but it is more common to do it in the view itself since you need access to request.body and the secret key.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Accept Payments with Paystack in Svelte and SvelteKit

Accept Paystack payments in SvelteKit using server endpoints for backend logic and Svelte components for the frontend. This guide covers initializing transactions, inline popup checkout, redirect checkout, and verification in a single full-stack SvelteKit project.

Read more

Handle Paystack Webhooks in Flask

Receive and verify Paystack webhooks in your Flask application. This guide covers accessing request.data for raw bytes, HMAC SHA-512 signature verification, processing charge.success events, and making handlers idempotent.

Read more

Build Paystack Subscriptions in ASP.NET Core

Implement Paystack recurring billing in ASP.NET Core. This guide covers creating plans, subscribing customers through transaction initialization, handling renewal webhooks with C#, and cancelling subscriptions.

Read more

Paystack Payment Requests and Invoices via API

Paystack payment requests let you send invoices to customers that include a payment link. The customer receives an email with the invoice details and a button to pay. This guide covers creating payment requests via the API, tracking their status, and building automated invoicing workflows.

Read more

Paystack Test Mode Working but Live Mode Failing

Your Paystack integration works perfectly in test mode but fails in live mode. The most common cause is an unactivated business account. Walk through this checklist to find and fix the exact issue.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.