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 transaction is paidSetting 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": "paid",
"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 request body. Always verify it against the raw body before processing.
How the signature is generated
1.NabooPay serializes the JSON payload and sends those exact bytes as the request body
2.HMAC SHA256 is computed over the body 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(raw_body: bytes, signature: str, secret_key: str) -> bool:
# Sign the raw request body exactly as received. Do not re-serialize it.
expected = hmac.new(
secret_key.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature or '', expected)
# Usage in Flask
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = os.environ["NABOOPAY_WEBHOOK_SECRET"]
@app.route('/webhooks/naboopay', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Signature')
raw_body = request.get_data()
if not verify_signature(raw_body, signature, SECRET_KEY):
return jsonify({"error": "Invalid signature"}), 401
payload = json.loads(raw_body)
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
- Verify the raw request body bytes, not a parsed and re-serialized version
- Make sure no JSON body-parsing middleware runs before your handler
- Use the test button in the NabooPay dashboard to debug