Local Webhook Testing
Testing webhooks locally is essential during development. paystack-django
provides two management commands and a WebhookTester utility to make
this straightforward.
Overview
Tool |
Purpose |
|---|---|
|
Start a Cloudflare Tunnel so Paystack can reach your local server |
|
Send a simulated webhook event to your local server |
|
Programmatic webhook simulation for automated tests |
Step-by-Step Workflow
Start your Django server:
python manage.py runserver
In a second terminal, start the tunnel:
python manage.py paystack_listen
Copy the printed Webhook URL and paste it in Paystack Dashboard → Settings → API Keys & Webhooks.
In a third terminal, send a test event:
python manage.py paystack_webhook_event charge.success
Or trigger a real event from Paystack (e.g. complete a test payment).
paystack_webhook_event
This command sends a properly signed webhook POST request to your local
server. It uses PAYSTACK['SECRET_KEY'] to generate the HMAC signature,
exactly as Paystack does in production.
Basic Usage
# Charge events
python manage.py paystack_webhook_event charge.success
# Transfer events
python manage.py paystack_webhook_event transfer.success --amount 100000
# Subscription events
python manage.py paystack_webhook_event subscription.create --email user@example.com
# Custom data
python manage.py paystack_webhook_event charge.success \
--data '{"reference": "my_ref_123", "amount": 75000}'
Options
event_type Event type (e.g. charge.success)
--url URL Target webhook URL (default: http://localhost:8000/webhooks/paystack/)
--data JSON Custom JSON payload (overrides sample data)
--reference REF Transaction reference (auto-generated if omitted)
--amount KOBO Amount in kobo (default: 50000)
--email EMAIL Customer email (default: test@example.com)
--list List all supported event types
Listing Available Events
python manage.py paystack_webhook_event --list
Output:
Paystack Webhook Event Types
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Charge:
● charge.success
Transfer:
● transfer.success
● transfer.failed
● transfer.reversed
Subscription:
● subscription.create
● subscription.disable
Refund:
● refund.processed
● refund.failed
Dispute:
● charge.dispute.create
● charge.dispute.resolve
...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
● = sample data included ○ = use --data for custom payload
WebhookTester (Programmatic)
For automated tests you can use the WebhookTester class directly:
from djpaystack.dev.webhook_tester import WebhookTester
tester = WebhookTester(
webhook_url='http://localhost:8000/webhooks/paystack/',
secret_key='sk_test_xxxxx',
)
# Send any event
response = tester.send_event('charge.success', {
'reference': 'test_001',
'amount': 50000,
'status': 'success',
'customer': {'email': 'test@example.com'},
})
assert response.status_code == 200
Convenience methods are available for common events:
tester.send_charge_success(reference='ref_001', amount=50000, email='a@b.com')
tester.send_subscription_create(email='a@b.com')
tester.send_transfer_success(amount=100000)
Django TestCase Example
import json, hashlib, hmac
from django.test import TestCase, RequestFactory
from unittest.mock import patch
from djpaystack.webhooks.handlers import webhook_handler
from djpaystack.webhooks.views import PaystackWebhookView
class WebhookIntegrationTest(TestCase):
def test_charge_success_webhook(self):
secret = 'sk_test_xxxxx'
payload = json.dumps({
'event': 'charge.success',
'data': {
'reference': 'test_ref',
'amount': 50000,
'status': 'success',
'customer': {'email': 'test@example.com'},
},
}).encode()
signature = hmac.new(
secret.encode(), payload, hashlib.sha512
).hexdigest()
factory = RequestFactory()
request = factory.post(
'/webhook/',
data=payload,
content_type='application/json',
HTTP_X_PAYSTACK_SIGNATURE=signature,
)
with patch('djpaystack.webhooks.handlers.paystack_settings') as mock:
mock.SECRET_KEY = secret
mock.ENABLE_MODELS = False
mock.ENABLE_SIGNALS = False
with patch.object(webhook_handler, 'verify_ip', return_value=True):
response = PaystackWebhookView.as_view()(request)
self.assertEqual(response.status_code, 200)
Troubleshooting
- “SECRET_KEY not configured”
Make sure
PAYSTACK['SECRET_KEY']is set in your Django settings.- Webhook returns 403 — Unauthorized IP
During local testing, the request comes from
127.0.0.1, which isn’t in Paystack’s IP whitelist. Either:Clear
ALLOWED_WEBHOOK_IPSin settings (allows all IPs), orAdd
127.0.0.1to the whitelist during development.
- Webhook returns 400 — Invalid signature
The
SECRET_KEYused to send the test event must match the one configured in your Django settings.- Connection refused
Make sure
python manage.py runserveris running on the expected port.