Webhook API
Receive event notifications directly on your server.
Authentication
Every request requires your API key in the headers.
Required Headers
Authorization
Bearer sk_live_XXXXXXXXXXXX
Content-Type
application/json
Overview
Instead of polling the API constantly, SpeedPesa Pro sends an HTTP POST to your URL every time a transaction status changes. Register your URL under Dashboard → Settings → Developer.
PAYIN Webhook Event
This event is sent when a customer completes a payment (collection / USSD-PUSH).
{
"event": "payment.completed",
"sent_at": "2026-07-31T12:00:00+03:00",
"data": {
"transaction_id": "STB-8FA21-3D91C",
"reference": "ORD-1001",
"direction": "credit",
"amount": "5000.00",
"currency": "TZS",
"status": "success",
"channel": "Tigo Pesa",
"phone": "255712345678",
"completed_at": "2026-07-31T12:00:00+03:00"
}
}PAYOUT Webhook Event
This event is sent when a payout to the recipient's mobile money succeeds.
{
"event": "payout.completed",
"sent_at": "2026-07-31T14:30:00+03:00",
"data": {
"transaction_id": "WDR-7C31A9",
"reference": "PAY-2001",
"direction": "debit",
"amount": "10000.00",
"fee": "660.00",
"currency": "TZS",
"status": "success",
"channel": "Vodacom Tanzania",
"phone": "255712345678",
"completed_at": "2026-07-31T14:30:00+03:00"
}
}FAILED Webhook Event
Sent when a transaction fails (wrong PIN, customer cancelled, or insufficient balance).
{
"event": "payment.failed",
"sent_at": "2026-07-31T12:02:10+03:00",
"data": {
"transaction_id": "STB-8FA21-3D91C",
"reference": "ORD-1001",
"status": "failed",
"message": "Mteja alighairi muamala."
}
}Webhook Secret
Every webhook you register gets its own secret (starts with whsec_). SpeedPesa Pro signs each event with it, so your server can prove the request really came from us.
- Where to find it: Dashboard → Settings → Developer → Webhook URL. The secret is shown under each registered URL with a copy button.
- How the signature is built: HMAC-SHA256 over timestamp + "." + raw request body, using your webhook secret as the key. The header format is t=<timestamp>,v1=<hex>.
- Keep the secret on your server only (environment variable) — never in browser JavaScript, and reject events older than 5 minutes.
X-SpeedPesa Pro-Event: payment.updated
X-SpeedPesa Pro-Timestamp: 1754000000
X-SpeedPesa Pro-Signature: t=1754000000,v1=9f2c… (HMAC-SHA256(timestamp + "." + body, whsec_…))Verifying the Signature
Every request carries a header X-SpeedPesa Pro-Signature with an HMAC SHA-256 of the request body using your webhook secret. Compare it before processing.
<?php
// Webhook secret: Dashboard -> Settings -> Developer -> Webhook URL (whsec_...)
$secret = 'whsec_XXXXXXXXXXXXXXXXXXXX';
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_SPEEDPESA_SIGNATURE'] ?? ''; // t=1754000000,v1=abc123...
// Toa timestamp (t) na saini (v1) kwenye header
$parts = [];
foreach (explode(',', $header) as $piece) {
[$k, $v] = array_pad(explode('=', trim($piece), 2), 2, '');
$parts[$k] = $v;
}
$timestamp = $parts['t'] ?? '';
$signature = $parts['v1'] ?? '';
// Kinga ya replay: kubali dakika 5 tu
if (!$timestamp || abs(time() - (int) $timestamp) > 300) {
http_response_code(400);
exit('Stale webhook');
}
$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// $event['status'], $event['transaction_id'], $event['amount'], $event['order_id']
if (($event['status'] ?? '') === 'success') {
// fungua oda / ongeza salio la mteja
}
http_response_code(200);
echo 'ok';