SpeedPesa Pro API v1

Webhook API

Receive event notifications directly on your server.

WEBHOOK · POST to youSpeedPesa Pro sends a POST to your server every time a transaction status changes.

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).

json
{
  "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.

json
{
  "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).

json
{
  "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.
headers
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
<?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';