Django Signals

paystack-django dispatches Django signals when webhook events are processed, allowing you to hook into the payment lifecycle without modifying library code.

Available Signals

All signals are defined in djpaystack.signals:

Signal

Triggered when

paystack_payment_successful

charge.success webhook received

paystack_subscription_created

subscription.create webhook received

paystack_subscription_cancelled

subscription.disable webhook received

paystack_subscription_not_renewing

subscription.not_renew webhook received

paystack_subscription_expiring_cards

subscription.expiring_cards webhook received

paystack_transfer_successful

transfer.success webhook received

paystack_transfer_failed

transfer.failed webhook received

paystack_transfer_reversed

transfer.reversed webhook received

paystack_refund_pending

refund.pending webhook received

paystack_refund_processing

refund.processing webhook received

paystack_refund_processed

refund.processed webhook received

paystack_refund_failed

refund.failed webhook received

paystack_dispute_created

charge.dispute.create webhook received

paystack_dispute_remind

charge.dispute.remind webhook received

paystack_dispute_resolved

charge.dispute.resolve webhook received

paystack_customeridentification_success

customeridentification.success webhook received

paystack_customeridentification_failed

customeridentification.failed webhook received

paystack_dedicatedaccount_assign_success

dedicatedaccount.assign.success webhook received

paystack_dedicatedaccount_assign_failed

dedicatedaccount.assign.failed webhook received

paystack_invoice_created

invoice.create webhook received

paystack_invoice_updated

invoice.update webhook received

paystack_invoice_payment_failed

invoice.payment_failed webhook received

paystack_paymentrequest_pending

paymentrequest.pending webhook received

paystack_paymentrequest_success

paymentrequest.success webhook received

Connecting to Signals

Use Django’s @receiver decorator:

from django.dispatch import receiver
from djpaystack.signals import (
    paystack_payment_successful,
)

@receiver(paystack_payment_successful)
def on_payment_success(sender, data, **kwargs):
    """Called when a charge.success webhook is processed."""
    reference = data['reference']
    amount = data['amount']  # in kobo
    # Fulfil the order, send receipt, etc.

Best Practice: Register in apps.py

Create a signals.py module in your app and import it from ready():

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myapp'

    def ready(self):
        import myapp.signals  # noqa: F401
# myapp/signals.py
from django.dispatch import receiver
from django.utils import timezone
from djpaystack.signals import paystack_payment_successful
from .models import Order

@receiver(paystack_payment_successful)
def fulfil_order(sender, data, **kwargs):
    try:
        order = Order.objects.get(reference=data['reference'])
        order.status = 'paid'
        order.paid_at = timezone.now()
        order.save()
    except Order.DoesNotExist:
        pass  # Log or handle missing order

Multiple Events

Subscribe to several signals in the same module:

from django.dispatch import receiver
from djpaystack.signals import (
    paystack_payment_successful,
    paystack_subscription_created,
    paystack_transfer_successful,
    paystack_refund_processed,
    paystack_dispute_created,
    paystack_invoice_created,
    paystack_customeridentification_success,
)

@receiver(paystack_payment_successful)
def on_payment(sender, data, **kwargs):
    ...

@receiver(paystack_subscription_created)
def on_subscription(sender, data, **kwargs):
    ...

@receiver(paystack_transfer_successful)
def on_transfer(sender, data, **kwargs):
    ...

@receiver(paystack_refund_processed)
def on_refund(sender, data, **kwargs):
    ...

@receiver(paystack_dispute_created)
def on_dispute(sender, data, **kwargs):
    ...

@receiver(paystack_invoice_created)
def on_invoice(sender, data, **kwargs):
    ...

@receiver(paystack_customeridentification_success)
def on_customer_verified(sender, data, **kwargs):
    ...

Disabling Signals

Set ENABLE_SIGNALS to False to suppress all signal dispatch:

PAYSTACK = {
    'SECRET_KEY': 'sk_...',
    'ENABLE_SIGNALS': False,
}

Best Practices

  1. Keep handlers fast — Offload long-running work to Celery or Django-Q.

  2. Use try/except — A failing handler should not crash the webhook response.

  3. Test in isolation — Send signals manually in unit tests:

    from djpaystack.signals import paystack_payment_successful
    
    paystack_payment_successful.send(
        sender=None,
        data={'reference': 'test_ref', 'amount': 50000},
    )
    
  4. Log signal events — Helps diagnose missed or double-processed payments.