Preview: the API is being built and these endpoints may change
Webhooks
Events, signatures and retries.
Add an endpoint under Developers > Webhooks. We send a POST with a JSON body for each event.
Events
| Event | When |
|---|---|
transaction.successful | A sale delivered and its fee was settled |
transaction.failed | A sale failed and its hold was released |
transaction.pending | A sale is waiting on the network's answer |
transaction.refunded | We refunded a sale |
wallet.funded | A top-up was credited |
wallet.low_balance | The wallet dropped below your alert level |
webhook.ping | A test you sent from the dashboard |
The body
{
"id": "0192f4c2-8d1e-7a3b-9c4f-5e6a7b8c9d0e",
"type": "transaction.successful",
"mode": "live",
"occurred_at": "2026-10-06T12:04:03Z",
"data": {
"reference": "txn_0192f4c1a7e84b6f9d2c3e5a7b9d1f30",
"client_reference": "order_1042",
"status": "successful",
"product_code": "MTN-VTU",
"network": "mtn",
"service": "airtime",
"destination": "+234********78",
"amount": 500.00,
"price": 2.00,
"completed_at": "2026-10-06T12:04:03Z"
}
}A failed sale adds failure_code. wallet.funded carries funding_id, amount, balance and gateway; wallet.low_balance carries balance and threshold. Amounts are naira, numbers with two decimals.
Checking the signature
Every request carries a SimConnect-Signature header: t=<unix time>,v1=<signature>. The signature is HMAC SHA-256 of <t>.<raw body> with your endpoint secret, in hex.
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && expected.length === v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Refuse anything older than five minutes, so a captured request cannot be replayed.
Retries
Answer with any 2xx within ten seconds to confirm. Anything else, or no answer, is retried from 30 seconds later, doubling each time, for up to 8 attempts. Each event has an id, also sent as the SimConnect-Event-Id header, so handle it once even if it arrives twice. Failed deliveries can be replayed from the dashboard.