Webhooks
Webhooks let your application receive real-time notifications when payment events occur in your NabooPay account — no polling required.
Available Events
payment_statusTriggered when a payment status changes (e.g., payment completed)Setting Up Webhooks
Create a webhook endpoint
Your endpoint must accept POST requests with application/json content type and return a 2xx status to acknowledge receipt.
Register your webhook URL
Go to your NabooPay dashboard, navigate to Settings → Integration, and create a new webhook.
Webhook Payload
{
"order_id": "order_123456",
"method_of_payment": ["wave", "orange_money"],
"selected_payment_method": "wave",
"amount": 10000,
"fees": 250,
"currency": "XOF",
"customer": {
"first_name": "John",
"last_name": "Doe",
"phone": "+221771234567",
"created_at": "2024-01-15T10:30:00Z"
},
"transaction_status": "completed",
"products": [
{
"name": "Product Name",
"price": 10000,
"quantity": 1,
"description": "Product description"
}
],
"is_escrow": false,
"is_merchant": false,
"fees_customer_side": true,
"success_url": "https://your-site.com/success",
"error_url": "https://your-site.com/error",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:35:00Z",
"paid_at": "2024-01-15T10:35:00Z"
}Verifying Signatures
Every webhook request includes an X-Signature header — an HMAC SHA256 hex digest of the compact JSON payload. Always verify it before processing.
How the signature is generated
1.The JSON payload is serialized in compact format (no extra whitespace)
2.HMAC SHA256 is computed using your webhook secret key
3.The result is hex-encoded and sent as X-Signature
import hmac
import hashlib
import json
def verify_signature(payload: dict, signature: str, secret_key: str) -> bool:
# Serialize payload to compact JSON (no extra whitespace)
payload_bytes = json.dumps(payload, separators=(',', ':')).encode('utf-8')
# Compute expected signature
expected = hmac.new(
secret_key.encode('utf-8'),
payload_bytes,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Usage in Flask
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = "your-secret-key-min-16-chars"
@app.route('/webhooks/naboopay', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Signature')
payload = request.get_json()
if not verify_signature(payload, signature, SECRET_KEY):
return jsonify({"error": "Invalid signature"}), 401
order_id = payload.get('order_id')
status = payload.get('transaction_status')
print(f"Payment {order_id} status: {status}")
return jsonify({"status": "received"}), 200Retry Behavior
Best Practices
Always verify signatures
Never process webhooks without first verifying the X-Signature header. Reject any request that fails verification with a 401.
Respond quickly
Return a 2xx response as fast as possible. If you need to do heavy processing, acknowledge receipt immediately and process asynchronously.
Handle duplicates
Your endpoint must be idempotent — the same webhook may be delivered more than once. Use the order_id to detect and skip duplicates.
Use HTTPS
Always use HTTPS for your webhook endpoint to ensure payloads are encrypted in transit.
Secure your secret
Store your webhook secret key in environment variables or a secrets manager — never hardcode it.
Log everything
Keep logs of all received webhooks for debugging, auditing, and replaying missed events.
Troubleshooting
Webhook not received?
- Verify your endpoint is publicly accessible (not localhost)
- Check that your URL is correct and uses HTTPS
- Ensure your server returns a 2xx status code
- Check firewall or proxy settings
Invalid signature?
- Ensure you're using the correct secret key
- Serialize the payload as compact JSON — no extra whitespace
- Read the raw request body, not a re-serialized parsed version
- Use the test button in the NabooPay dashboard to debug