NabooPay

Webhooks

Webhooks let your application receive real-time notifications when payment events occur in your NabooPay account — no polling required.

Available Events

EventDescription
payment_statusTriggered when a transaction is paid

Setting Up Webhooks

1

Create a webhook endpoint

Your endpoint must accept POST requests with application/json content type and return a 2xx status to acknowledge receipt.

2

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"}), 200

Retry Behavior

ParameterValue
Retry attemptsUp to 3 retries
Retry interval5 minutes between each retry
Timeout per request5 seconds
After all retriesDelivery marked as failed

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