Webhook Integration Guide

Our system will forward payment notifications to your configured webhook endpoint. Each webhook request is signed with your Webhook Secret, which you can generate in your merchant dashboard.

Webhook Request

POST
https://yourdomain.com/my-webhook-endpoint

Headers

Header Description Required
Content-Type application/json Yes
X-Webhook-Signature HMAC-SHA256 signature of the payload Yes

Request Payload

{
  "status": "success",
  "customer_reference": "12345",
  "internal_reference": "abc123",
  "currency": "UGX",
  "charge": 300,
  "message": "Payment completed",
  "msisdn": "256700000000",
  "provider": "MTN",
  "completed_at": "2025-09-04T10:15:00Z"
}

Signature Verification

Each request includes a header: X-Webhook-Signature. This header contains an HMAC-SHA256 signature of the JSON payload, generated using your Webhook Secret.

You must compute the signature on your side and compare it with the header.

Example Implementations

PHP Implementation

<?php
// PHP implementation for webhook signature verification
$payload = file_get_contents("php://input");
$headers = getallheaders();
$receivedSignature = $headers["X-Webhook-Signature"] ?? null;
$secret = getenv("WEBHOOK_SECRET");

$expectedSignature = hash_hmac("sha256", $payload, $secret);

if ($receivedSignature !== $expectedSignature) {
    http_response_code(400);
    echo "Invalid signature";
    exit;
}

error_log("Webhook verified successfully: " . $payload);
http_response_code(200);
echo "Webhook processed successfully";
?>

Node.js (Express) Implementation

const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.json());

app.post('/my-webhook-endpoint', (req, res) => {
  const payload = req.body;
  const receivedSignature = req.headers['x-webhook-signature'];
  const secret = process.env.WEBHOOCK_SECRET;

  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  if (receivedSignature !== expectedSignature) {
    console.log('Invalid signature', { received: receivedSignature, expected: expectedSignature });
    return res.status(400).send('Invalid signature");
  }

  console.log('Webhook verified successfully', payload);
  res.status(200).send('Webhook processed successfully');
});

app.listen(3000, () => console.log('Listening on port 3000'));

Python (Flask) Implementation

import hmac
import hashlib
import os
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/my-webhook-endpoint', methods=['POST'])
def webhook():
    payload = request.get_data(as_text=True)
    received_signature = request.headers.get("X-Webhook-Signature")
    secret = os.getenv("WEBHOOK_SECRET")

    expected_signature = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()

    if received_signature != expected_signature:
        print("Invalid signature")
        return "Invalid signature", 400

    print("Webhook verified successfully", request.json)
    return "Webhook processed successfully", 200

if __name__ == '__main__':
    app.run(port=3000)

Testing with Curl

curl "https://yourdomain.com/my-webhook-endpoint" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: <computed-signature>" \
  -d '{
    "status": "success",
    "customer_reference": "12345",
    "internal_reference": "abc123",
    "currency": "UGX",
    "charge": 300,
    "message": "Payment completed",
    "msisdn": "256700000000",
    "provider": "MTN",
    "completed_at": "2025-09-04T10:15:00Z"
  }'

Security Notes

Important

Make sure to test your webhook implementation thoroughly before going live. Use the test mode if available to verify your integration works correctly.