Skip to main content
Version: v1

NabooPay Webhook Integration Guide

This guide explains how to integrate NabooPay webhooks to receive real-time notifications about payment events.

Overview

Webhooks allow your application to receive automatic notifications when events occur in your NabooPay account. Instead of polling our API for updates, webhooks push data to your server in real-time.

Available Events

EventDescription
payment_statusTriggered when a payment status changes (e.g., payment completed)

Setting Up Webhooks

1. Create a Webhook Endpoint

First, create an endpoint on your server to receive webhook notifications. Your endpoint must:

  • Accept POST requests
  • Accept application/json content type
  • Return a 2xx status code to acknowledge receipt

2. Register Your Webhook

To create a webhook, go to https://platform.naboopay.com/parametre/ and then go to integration where you can create your webhook.

Webhook Payload

V1 Payload (Legacy)

When a payment event occurs, NabooPay sends a POST request to your webhook URL with the following payload:

{
"order_id": "order_123456",
"amount": 10000,
"currency": "XOF",
"created_at": "2024-01-15T10:30:00Z",
"transaction_status": "paid",
"payment_method": "wave"
}

V2 Payload (Default for new webhooks)

Newer webhooks use an expanded payload with more details:

{
"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 Webhook Signatures

All webhook requests include an X-Signature header containing an HMAC SHA256 signature. Always verify this signature to ensure the request is authentic.

How Signature is Generated

  1. The JSON payload is serialized (compact format, no extra whitespace)
  2. An HMAC SHA256 hash is computed using your secret key
  3. The hash is hex-encoded

Verification Examples

Python

import hmac
import hashlib
import json

def verify_signature(payload: dict, signature: str, secret_key: str) -> bool:
"""Verify NabooPay webhook signature."""
# Serialize payload to compact JSON (no extra whitespace)
payload_bytes = json.dumps(payload, separators=(',', ':')).encode('utf-8')

# Compute expected signature
expected_signature = hmac.new(
secret_key.encode('utf-8'),
payload_bytes,
hashlib.sha256
).hexdigest()

# Compare signatures (timing-safe comparison)
return hmac.compare_digest(signature, expected_signature)

# 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

# Process the webhook
order_id = payload.get('order_id')
status = payload.get('transaction_status')

print(f"Payment {order_id} status: {status}")

return jsonify({"status": "received"}), 200

Node.js

const crypto = require('crypto');

function verifySignature(payload, signature, secretKey) {
// Serialize payload to compact JSON
const payloadString = JSON.stringify(payload);

// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(payloadString)
.digest('hex');

// Compare signatures (timing-safe comparison)
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

// Usage in Express
const express = require('express');
const app = express();

const SECRET_KEY = 'your-secret-key-min-16-chars';

app.post('/webhooks/naboopay', express.json(), (req, res) => {
const signature = req.headers['x-signature'];
const payload = req.body;

if (!verifySignature(payload, signature, SECRET_KEY)) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Process the webhook
const { order_id, transaction_status } = payload;
console.log(`Payment ${order_id} status: ${transaction_status}`);

res.status(200).json({ status: 'received' });
});

PHP

<?php

function verifySignature($payload, $signature, $secretKey) {
// Serialize payload to compact JSON
$payloadString = json_encode($payload, JSON_UNESCAPED_SLASHES);

// Compute expected signature
$expectedSignature = hash_hmac('sha256', $payloadString, $secretKey);

// Compare signatures (timing-safe comparison)
return hash_equals($expectedSignature, $signature);
}

// Usage
$secretKey = 'your-secret-key-min-16-chars';

$payload = json_decode(file_get_contents('php://input'), true);
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

if (!verifySignature($payload, $signature, $secretKey)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}

// Process the webhook
$orderId = $payload['order_id'];
$status = $payload['transaction_status'];

error_log("Payment $orderId status: $status");

http_response_code(200);
echo json_encode(['status' => 'received']);

Go

package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net/http"
)

func verifySignature(payload []byte, signature, secretKey string) bool {
h := hmac.New(sha256.New, []byte(secretKey))
h.Write(payload)
expectedSignature := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
secretKey := "your-secret-key-min-16-chars"

signature := r.Header.Get("X-Signature")
body, _ := io.ReadAll(r.Body)

if !verifySignature(body, signature, secretKey) {
http.Error(w, `{"error":"Invalid signature"}`, http.StatusUnauthorized)
return
}

var payload map[string]interface{}
json.Unmarshal(body, &payload)

// Process the webhook
orderId := payload["order_id"].(string)
status := payload["transaction_status"].(string)

log.Printf("Payment %s status: %s", orderId, status)

w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"received"}`))
}

Retry Behavior

If your webhook endpoint fails to respond with a 2xx status code, NabooPay will retry the delivery:

  • Retry attempts: Up to 3 retries
  • Retry interval: 5 minutes between each retry
  • Timeout: 5 seconds per request

After all retry attempts are exhausted, the webhook delivery is considered failed.

Best Practices

  1. Always verify signatures - Never process webhooks without verifying the signature first.

  2. Respond quickly - Return a 2xx response as fast as possible. Process the webhook asynchronously if needed.

  3. Handle duplicates - Your endpoint should be idempotent. The same webhook might be delivered multiple times.

  4. Use HTTPS - Always use HTTPS for your webhook endpoint to ensure data is encrypted in transit.

  5. Store your secret securely - Keep your webhook secret key in environment variables or a secrets manager.

  6. Log webhook events - Keep logs of received webhooks for debugging and auditing purposes.

Troubleshooting

Webhook not received

  • Verify your endpoint is publicly accessible
  • Check that your URL is correct and uses HTTPS
  • Ensure your server returns a 2xx status code
  • Check your firewall settings

Invalid signature

  • Ensure you're using the correct secret key
  • Verify you're serializing the payload as compact JSON (no extra whitespace)
  • Check that you're reading the raw request body, not a parsed version

Test webhook with the API

Use the test button in https://platform.naboopay.com/parametre/