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. Build Paystack Subscriptions in Django REST Framework
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Build Paystack Subscriptions in Django REST Framework

To build Paystack subscriptions in DRF, create API endpoints for subscribing customers (passing a plan_code to the initialize endpoint), handle subscription webhooks (subscription.create, charge.success, invoice.payment_failed), track status in a Subscription model, and provide endpoints for viewing and cancelling subscriptions.

In this article

  1. 1. Subscribe Endpoint
  2. 2. Subscription Model and Serializer
  3. 3. Subscription Webhook Handlers
  4. 4. Status and Cancellation Endpoints
  5. 5. Subscription Permission Class
  6. 6. Ship Payments Faster

Subscribe Endpoint

# payments/views.py
class SubscribeView(APIView):
    def post(self, request):
        plan_code = request.data.get('plan_code')
        email = request.user.email

        response = requests.post(
            'https://api.paystack.co/transaction/initialize',
            json={
                'email': email,
                'plan': plan_code,
                'callback_url': settings.FRONTEND_URL + '/subscription/callback',
            },
            headers={
                'Authorization': 'Bearer ' + settings.PAYSTACK_SECRET_KEY,
                'Content-Type': 'application/json',
            },
        )

        result = response.json()

        if not result.get('status'):
            return Response({'error': result.get('message')}, status=400)

        return Response({
            'authorization_url': result['data']['authorization_url'],
            'reference': result['data']['reference'],
        })

Subscription Model and Serializer

# payments/models.py
class Subscription(models.Model):
    user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
    subscription_code = models.CharField(max_length=100, unique=True)
    plan_code = models.CharField(max_length=100)
    email_token = models.CharField(max_length=100)
    status = models.CharField(max_length=20, default='active')
    next_payment_date = models.DateTimeField(null=True)
    created_at = models.DateTimeField(auto_now_add=True)

# payments/serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subscription
        fields = ['subscription_code', 'plan_code', 'status', 'next_payment_date', 'created_at']

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Subscription Webhook Handlers

# payments/services.py
from django.contrib.auth.models import User
from .models import Subscription

def handle_subscription_create(data):
    email = data['customer']['email']
    try:
        user = User.objects.get(email=email)
    except User.DoesNotExist:
        return

    Subscription.objects.update_or_create(
        user=user,
        defaults={
            'subscription_code': data['subscription_code'],
            'plan_code': data['plan']['plan_code'],
            'email_token': data.get('email_token', ''),
            'status': 'active',
            'next_payment_date': data.get('next_payment_date'),
        },
    )

def handle_invoice_failed(data):
    sub_code = data.get('subscription', {}).get('subscription_code')
    if sub_code:
        Subscription.objects.filter(subscription_code=sub_code).update(status='past_due')

def handle_subscription_disable(data):
    sub_code = data.get('subscription_code')
    if sub_code:
        Subscription.objects.filter(subscription_code=sub_code).update(status='cancelled')

Status and Cancellation Endpoints

# payments/views.py
class SubscriptionStatusView(APIView):
    def get(self, request):
        try:
            sub = request.user.subscription
        except Subscription.DoesNotExist:
            return Response({'has_subscription': False})

        serializer = SubscriptionSerializer(sub)
        return Response({'has_subscription': True, 'subscription': serializer.data})

class CancelSubscriptionView(APIView):
    def post(self, request):
        try:
            sub = request.user.subscription
        except Subscription.DoesNotExist:
            return Response({'error': 'No subscription found'}, status=404)

        response = requests.post(
            'https://api.paystack.co/subscription/disable',
            json={'code': sub.subscription_code, 'token': sub.email_token},
            headers={
                'Authorization': 'Bearer ' + settings.PAYSTACK_SECRET_KEY,
                'Content-Type': 'application/json',
            },
        )

        result = response.json()
        if result.get('status'):
            sub.status = 'cancelled'
            sub.save()
            return Response({'message': 'Subscription cancelled'})

        return Response({'error': result.get('message')}, status=400)

Subscription Permission Class

# payments/permissions.py
from rest_framework.permissions import BasePermission

class HasActiveSubscription(BasePermission):
    message = 'An active subscription is required.'

    def has_permission(self, request, view):
        if not request.user or not request.user.is_authenticated:
            return False
        try:
            return request.user.subscription.status == 'active'
        except Exception:
            return False

Use it on any APIView:

class PremiumContentView(APIView):
    permission_classes = [IsAuthenticated, HasActiveSubscription]

    def get(self, request):
        return Response({'content': 'Premium data here'})

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

  • ✓DRF serializers validate subscription requests. Use them to ensure valid plan codes and email addresses.
  • ✓The first payment creates the subscription. Pass the plan_code when initializing the transaction.
  • ✓Track subscription status in a Django model. Update it via webhook events, not by polling Paystack.
  • ✓Use DRF permissions to gate API endpoints behind active subscription checks.
  • ✓Store the email_token from subscription.create webhooks. You need it for cancellation.
  • ✓Webhook events are the source of truth for subscription state changes.

Frequently Asked Questions

Can I offer multiple subscription tiers?
Yes. Create separate plans in Paystack with different amounts and intervals. Store the plan_code in your app config and let the frontend pass the desired plan_code when subscribing.
How do I handle plan upgrades in DRF?
Cancel the current subscription via the disable endpoint, then create a new subscription on the new plan. Handle prorating in your application logic.
Should I check subscription status on every request?
Use the HasActiveSubscription permission class on protected endpoints. It checks your database, which is updated by webhooks. This is fast and does not require calling the Paystack API on every request.

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 Pages: When No-Code Beats Code

Paystack Payment Pages let you collect payments without writing any code. Create a page, share the link, and customers can pay. This guide covers when payment pages are the right tool, how to create and customize them, and when you should switch to a code-based integration.

Read more

Paystack Live Keys Rejected After Business Activation

Your Paystack business is activated but live keys still return "Invalid key" or fail silently. This is usually a wrong environment variable, an incomplete activation, or a key that needs to be regenerated.

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.