Overview
Fingo Pay delivers asynchronous events (e.g., final transaction results) via HTTP POST to your endpoint.
Registering webhook URLs
- Provide a per-request
webhookUrl (as in C2B/B2C endpoints), and/or
- Configure an account-level default webhook with our team (recommended for reliability).
Security — Signature verification
We sign every webhook with an HMAC SHA‑256 signature using your webhook secret.
Headers:
X-Fingo-Signature: t=<unix>, v1=<hex_hmac>
X-Fingo-Event-Id: evt_...
Compute v1 = hex(hmac_sha256(secret, t + ”.” + raw_body)) and require timestamp t within 5 minutes.
import crypto from 'node:crypto'
import express from 'express'
const app = express()
app.use(express.raw({ type: 'application/json' }))
function verifySignature(secret, timestamp, rawBody, signature) {
const payload = `${timestamp}.${rawBody.toString('utf8')}`
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'))
}
app.post('/webhooks/fingo', (req, res) => {
const sig = req.header('X-Fingo-Signature') || ''
const evtId = req.header('X-Fingo-Event-Id')
const t = sig.split(',')[0]?.split('=')[1]
const v1 = sig.split(',')[1]?.split('=')[1]
if (!t || !v1) return res.status(400).end()
const fiveMinutes = 5 * 60
if (Math.abs(Math.floor(Date.now()/1000) - Number(t)) > fiveMinutes) {
return res.status(400).send('timestamp_out_of_window')
}
const ok = verifySignature(process.env.FINGO_WEBHOOK_SECRET, t, req.body, v1)
if (!ok) return res.status(400).send('invalid_signature')
const event = JSON.parse(req.body)
// handle event.type
res.sendStatus(200)
})
Delivery & retries
- Expect 2xx within 10 seconds.
- Retries use exponential backoff for up to 24 hours.
- Each event is delivered at least once; de‑duplicate using
X-Fingo-Event-Id.
Event types
-
transaction.created
-
transaction.processing
-
transaction.succeeded
-
transaction.failed
-
transaction.creation_failed
-
transaction.reversed
transaction.* events apply to all transaction flows, including collections and payouts.
Webhook Event Reference
View detailed webhook event schemas and examples for transaction.* event types
Example payloads
C2B (M-Pesa Charge) Webhooks
Sent when the customer successfully completes the STK Push payment.{
"id": "evt_k8m2x9p4lq7n",
"type": "transaction.succeeded",
"created": 1736697600,
"data": {
"id": "txn_01j7b6f9p5y9h",
"merchantTransactionId": "mtx_123",
"status": "completed",
"message": "The service was accepted successfully",
"amount": 10000,
"currency": "KES",
"type": "charge",
"paymentMethod": "mobile_money",
"chargedPhone": "254712345678",
"narration": "Invoice #1234",
"processor": "mpesa",
"processorReference": "ODI31ABC123XYZ",
"createdAt": "2025-01-12T14:30:00.000Z",
"updatedAt": "2025-01-12T14:30:45.000Z",
"metadata": {
"orderId": "ORD-987",
"source": "api"
}
}
}
Sent when the customer cancels, times out, or the payment fails for any reason.{
"id": "evt_p3q7r2s5tw8y",
"type": "transaction.failed",
"created": 1736697800,
"data": {
"id": "txn_01j7b8x2m4n6k",
"merchantTransactionId": "mtx_456",
"status": "failed",
"message": "Request cancelled by user",
"amount": 5000,
"currency": "KES",
"type": "charge",
"paymentMethod": "mobile_money",
"chargedPhone": "254722334455",
"narration": "Order payment",
"processor": "mpesa",
"processorReference": "QWE78DEF456GHI",
"createdAt": "2025-01-12T15:00:00.000Z",
"updatedAt": "2025-01-12T15:01:30.000Z",
"metadata": {
"orderId": "ORD-654",
"source": "api"
}
}
}
Sent when the transaction fails during creation (e.g., shortcode not found, account not configured).{
"id": "evt_z9x8w7v6ut5s",
"type": "transaction.failed",
"created": 1736698000,
"data": {
"id": "txn_01j7c2a4b6c8d",
"merchantTransactionId": "mtx_789",
"status": "failed",
"message": "Shortcode not found",
"amount": 15000,
"currency": "KES",
"type": "charge",
"paymentMethod": "mobile_money",
"processor": "mpesa",
"createdAt": "2025-01-12T15:30:00.000Z",
"updatedAt": "2025-01-12T15:30:00.000Z",
"metadata": {
"reason": "Shortcode not found"
}
}
}
Payout transaction webhooks
Creation Failed - Business Logic Error
Transaction Succeeded (Payout)
Transaction Failed (Payout)
Sent when transaction creation fails due to non-retriable errors like insufficient balance, duplicate transaction, or account not found.{
"id": "evt_k8x9m2y4abc1",
"type": "transaction.creation_failed",
"created": 1737043200,
"data": {
"id": "txn_abc123xyz789",
"merchantTransactionId": "order_12345",
"status": "failed",
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient balance on payout account"
},
"amount": 100000,
"currency": "KES",
"type": "payment",
"paymentMethod": "mobile_money",
"processor": "mpesa",
"createdAt": "2026-01-16T12:00:00.000Z",
"updatedAt": "2026-01-16T12:00:00.000Z"
}
}
Other error code examples
{
"error": {
"code": "DUPLICATE_TRANSACTION",
"message": "Duplicate merchantTransactionId: order_12345"
}
}
{
"error": {
"code": "ACCOUNT_NOT_FOUND",
"message": "Debit account not found"
}
}
{
"error": {
"code": "NO_PAYOUT_ACCOUNT",
"message": "No payout account configured"
}
}
Sent when a payout transaction completes successfully.{
"id": "evt_p7m3n5q2def4",
"type": "transaction.succeeded",
"created": 1737043260,
"data": {
"object": {
"id": "txn_abc123xyz789",
"merchantTransactionId": "order_12345",
"status": "completed",
"message": "B2C transfer completed",
"amount": 100000,
"currency": "KES",
"type": "payment",
"paymentMethod": "mobile_money",
"chargedPhone": "+254712345678",
"narration": "Salary payment for January",
"processor": "mpesa",
"processorReference": "AG_20260116_1234567890abcdef",
"externalReference": "B2C_XYZ123ABC",
"createdAt": "2026-01-16T12:00:00.000Z",
"updatedAt": "2026-01-16T12:01:00.000Z",
"metadata": {
"employeeId": "EMP-001",
"source": "api"
}
}
}
}
Sent when a payout transaction fails at provider level.{
"id": "evt_f4k2j8r9ghi5",
"type": "transaction.failed",
"created": 1737043260,
"data": {
"object": {
"id": "txn_abc123xyz789",
"merchantTransactionId": "order_12345",
"status": "failed",
"message": "The initiator information is invalid.",
"amount": 100000,
"currency": "KES",
"type": "payment",
"paymentMethod": "mobile_money",
"chargedPhone": "+254712345678",
"narration": "Salary payment for January",
"processor": "mpesa",
"processorReference": "AG_20260116_1234567890abcdef",
"createdAt": "2026-01-16T12:00:00.000Z",
"updatedAt": "2026-01-16T12:01:00.000Z",
"metadata": {
"employeeId": "EMP-001",
"source": "api",
"error": {
"reason": "The initiator information is invalid."
}
}
}
}
}