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 payment status changes (e.g., payment completed)

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