To handle Paystack webhooks in Flask, create a POST route that reads request.data (raw bytes), computes an HMAC SHA-512 hash using your secret key, and compares it to the X-Paystack-Signature header. Parse the JSON and process the event. Return an empty response with status 200.
# app.py
import hmac
import hashlib
import json
import os
from flask import Flask, request
app = Flask(__name__)
PAYSTACK_SECRET = os.environ.get('PAYSTACK_SECRET_KEY')
@app.route('/webhooks/paystack', methods=['POST'])
def paystack_webhook():
signature = request.headers.get('X-Paystack-Signature', '')
if not signature:
return 'Missing signature', 400
computed_hash = hmac.new(
PAYSTACK_SECRET.encode('utf-8'),
msg=request.data,
digestmod=hashlib.sha512,
).hexdigest()
if not hmac.compare_digest(computed_hash, signature):
return 'Invalid signature', 400
event = json.loads(request.data)
process_event(event)
return '', 200
request.data in Flask gives you the raw bytes of the request body. This is exactly what you need for HMAC computation. No special middleware or configuration is required.
def process_event(event):
event_type = event.get('event')
data = event.get('data', {})
if event_type == 'charge.success':
handle_successful_payment(data)
elif event_type == 'charge.failed':
print('Payment failed: ' + str(data.get('reference')))
elif event_type == 'subscription.create':
handle_new_subscription(data)
elif event_type == 'invoice.payment_failed':
handle_failed_renewal(data)
def handle_successful_payment(data):
from helpers import verify_and_fulfill
reference = data.get('reference')
if reference:
verify_and_fulfill(reference, db)
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
# test_webhook.py
import hmac
import hashlib
import json
def test_valid_webhook(client):
payload = json.dumps({
'event': 'charge.success',
'data': {'reference': 'test_123', 'amount': 500000},
})
signature = hmac.new(
b'sk_test_xxx',
msg=payload.encode(),
digestmod=hashlib.sha512,
).hexdigest()
response = client.post(
'/webhooks/paystack',
data=payload,
content_type='application/json',
headers={'X-Paystack-Signature': signature},
)
assert response.status_code == 200
npx ngrok http 5000
Set the ngrok HTTPS URL as your webhook URL in the Paystack dashboard: https://abc123.ngrok.io/webhooks/paystack
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