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.
https://yourdomain.com/my-webhook-endpoint
Header | Description | Required |
---|---|---|
Content-Type | application/json | Yes |
X-Webhook-Signature | HMAC-SHA256 signature of the payload | Yes |
{ "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" }
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.
<?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"; ?>
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'));
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)
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" }'
X-Webhook-Signature
before trusting the payload.200 OK
only after processing successfully.Make sure to test your webhook implementation thoroughly before going live. Use the test mode if available to verify your integration works correctly.