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. Verify Paystack Payments in Django
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Verify Paystack Payments in Django

To verify a Paystack payment in Django, use the requests library to GET https://api.paystack.co/transaction/verify/:reference with your secret key. Check that data.data.status is "success", the amount matches your Order model, and the currency is correct. Use update() with a filter on paid=False for idempotent fulfillment.

In this article

  1. 1. Basic Verification Function
  2. 2. Full Verification with Amount Check
  3. 3. Using Verification in the Callback View
  4. 4. Retry Logic for Network Failures
  5. 5. Common Verification Mistakes
  6. 6. Ship Payments Faster

Basic Verification Function

# payments/utils.py
import requests
from django.conf import settings

def verify_paystack_transaction(reference):
    """Call Paystack verify endpoint and return the result."""
    headers = {
        'Authorization': 'Bearer ' + settings.PAYSTACK_SECRET_KEY,
    }

    response = requests.get(
        'https://api.paystack.co/transaction/verify/' + reference,
        headers=headers,
    )

    return response.json()

This function returns the raw Paystack response. Your views and webhook handlers will use it and apply their own business logic on top.

Full Verification with Amount Check

# payments/utils.py (continued)
from django.utils import timezone
from .models import Order

def verify_and_fulfill(reference):
    """Verify payment and fulfill the order idempotently."""
    # 1. Look up the order
    try:
        order = Order.objects.get(reference=reference)
    except Order.DoesNotExist:
        return {'error': 'Order not found'}

    if order.paid:
        return {'success': True, 'already_fulfilled': True}

    # 2. Verify with Paystack
    result = verify_paystack_transaction(reference)

    if not result.get('status'):
        return {'error': 'Verification request failed'}

    data = result['data']

    if data['status'] != 'success':
        return {'error': 'Payment not successful', 'paystack_status': data['status']}

    # 3. Check amount
    if data['amount'] != order.amount:
        return {'error': 'Amount mismatch: expected ' + str(order.amount) + ' got ' + str(data['amount'])}

    # 4. Check currency
    if data['currency'] != order.currency:
        return {'error': 'Currency mismatch'}

    # 5. Idempotent fulfillment using update() with filter
    updated = Order.objects.filter(
        reference=reference,
        paid=False,
    ).update(paid=True, paid_at=timezone.now())

    if updated == 0:
        return {'success': True, 'already_fulfilled': True}

    # 6. Deliver value
    # send_confirmation_email(order.email, order)

    return {'success': True, 'already_fulfilled': False}

The Order.objects.filter(reference=reference, paid=False).update() pattern is atomic in Django. If two requests arrive simultaneously (webhook and callback), only one of them will update the row. The other gets updated == 0 and skips fulfillment.

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Using Verification in the Callback View

# payments/views.py
from django.shortcuts import render
from .utils import verify_and_fulfill

def payment_callback(request):
    reference = request.GET.get('reference') or request.GET.get('trxref')

    if not reference:
        return render(request, 'payments/failed.html', {'error': 'Missing reference'})

    result = verify_and_fulfill(reference)

    if result.get('success'):
        return render(request, 'payments/success.html', {'reference': reference})

    return render(request, 'payments/failed.html', {'error': result.get('error')})

The same verify_and_fulfill function should be called from your webhook handler. This ensures consistent verification logic regardless of which notification path fires first.

Retry Logic for Network Failures

# payments/utils.py
import time

def verify_with_retry(reference, max_retries=3):
    """Verify with exponential backoff on failure."""
    for attempt in range(max_retries):
        try:
            result = verify_paystack_transaction(reference)
            if result.get('status') is not None:
                return result
        except requests.exceptions.RequestException as e:
            print('Verify attempt ' + str(attempt + 1) + ' failed: ' + str(e))

        if attempt < max_retries - 1:
            time.sleep(2 ** attempt)  # 1s, 2s, 4s

    return None

If all retries fail, log the reference for manual investigation. The webhook will independently deliver the event, so the order can still be fulfilled through that path.

Common Verification Mistakes

  • Trusting form data for the amount. Always compare the Paystack amount against your Order model, not against what the user submitted in the form.
  • Not URL-encoding the reference. If your reference contains special characters, the verify URL will break. Use urllib.parse.quote() or let requests handle it.
  • Mixing test and live keys. Initializing with sk_test_ but verifying with sk_live_ returns "Transaction not found."
  • Forgetting to check currency. An attacker could pay in a different currency. Always verify the currency matches.
  • Using get() instead of filter().update(). Using order.save() after modifying paid=True is not atomic. Another request could save between your get and save.

Ship Payments Faster

This guide is part of the Paystack integration guides by language and framework series. For the full flow, see Accept Payments with Paystack in Django.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Verification is a single GET request to the Paystack API using the requests library.
  • ✓Always check three things: transaction status equals "success", amount matches your Order model, and currency is correct.
  • ✓Use Django ORM update() with a filter for atomic, idempotent fulfillment that prevents double-granting.
  • ✓Create a reusable verify_and_fulfill function that both your callback view and webhook handler call.
  • ✓If the verify request fails due to network issues, retry. The transaction state on Paystack does not change.
  • ✓Never trust the redirect URL parameters as proof of payment. Always verify server-side.

Frequently Asked Questions

Can I verify a transaction multiple times?
Yes. The Paystack verify endpoint is idempotent. It returns the same result for the same reference every time.
Should I use select_for_update for concurrency safety?
filter().update() is sufficient for most cases because it is a single atomic SQL statement. Use select_for_update() if your fulfillment logic involves multiple queries that must be consistent.
How long does Paystack keep transaction data?
Paystack stores transaction data indefinitely. You can verify transactions months or years after they were created.
What if the customer pays but my callback view crashes?
This is why webhooks matter. Even if your callback view fails, the webhook will independently deliver the charge.success event. Your webhook handler calls the same verify_and_fulfill function and fulfills the order.

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

Handling Abandoned Paystack Checkouts

A customer clicks "Pay," the Paystack checkout opens, and then they close the tab. The transaction sits as "abandoned" in your dashboard. This guide covers how to detect abandoned checkouts, recover lost sales with follow-up emails, and reduce abandonment through better checkout design.

Read more

Paystack Inline Script Not Loading

The Paystack Inline JS script fails to load when the URL is wrong, your Content Security Policy blocks it, an ad blocker intercepts it, or the network request fails. This guide covers every cause and shows you how to build a fallback so payments work even when the script is blocked.

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.