Skip to main content

Webhooks

Receive real-time notifications when events occur in the banking platform — transfers completing, loan approvals, fraud alerts, and more.

Setup

  1. Log in to the dashboard and navigate to Settings → Webhooks.
  2. Add your webhook URL and select the event types you want to receive.
  3. Save the webhook secret — it's used for signature verification and is only shown once.

You can also register webhooks programmatically via the API.

Payload Format

Every webhook delivery is an HTTP POST with a JSON body:

{
"id": "evt_a1b2c3d4",
"eventType": "transaction_completed",
"tenantId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-02-27T14:22:00Z",
"data": {
"transactionId": "t9a8b7c6-...",
"reference": "FMFB20260227001234",
"amount": 100000.00,
"status": "completed",
"fromAccount": "0000123456",
"toAccount": "0000654321"
}
}

Headers

HeaderDescription
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 signature of the request body
X-Webhook-EventEvent type (e.g. transaction_completed)
X-Webhook-IDUnique delivery ID for idempotency
X-Webhook-TimestampISO 8601 timestamp

Signature Verification

Verify the X-Webhook-Signature header to confirm the payload came from the Banking API and wasn't tampered with.

The signature is computed as HMAC-SHA256(webhook_secret, raw_request_body) and sent as a hex string.

const crypto = require("crypto");

function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

// Express middleware
app.post("/webhooks/banking", (req, res) => {
const signature = req.headers["x-webhook-signature"];
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);

if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}

// Process the event
handleEvent(req.body);
res.status(200).json({ received: true });
});
warning

Always use constant-time comparison (timingSafeEqual, hmac.compare_digest, hmac.Equal) to prevent timing attacks.

Event Types

EventDescriptionTrigger
transaction_completedTransfer or payment succeededTransfer settles
transaction_failedTransfer or payment failedTransfer rejected by NIBSS/provider
loan_applicationLoan application submittedUser applies for a loan
loan_approvedLoan application approvedFinal approval level passed
loan_disbursedLoan funds disbursedAmount credited to wallet
security_alertSecurity event detectedPassword change, new device login
fraud_alertPotential fraud flaggedRisk score exceeds threshold
kyc_requiredKYC action neededUser reaches a limit requiring higher KYC
limit_exceededTransaction limit breachedDaily/monthly limit hit
account_updateAccount status changedWallet frozen, KYC level upgraded
payment_reminderUpcoming payment dueLoan repayment or bill due soon
ai_insightAI-generated insightSpending anomaly detected

Example Payloads

transaction_completed

{
"id": "evt_tx_001",
"eventType": "transaction_completed",
"tenantId": "550e8400-...",
"timestamp": "2026-02-27T14:22:00Z",
"data": {
"transactionId": "t9a8b7c6-...",
"type": "external_transfer",
"reference": "FMFB20260227001234",
"amount": 100000.00,
"fees": 100.00,
"status": "completed",
"fromAccount": "0000123456",
"toAccount": "0000654321",
"toBank": "Access Bank"
}
}

fraud_alert

{
"id": "evt_fraud_001",
"eventType": "fraud_alert",
"tenantId": "550e8400-...",
"timestamp": "2026-02-27T14:22:00Z",
"data": {
"userId": "b47ac10b-...",
"riskScore": 85,
"riskLevel": "critical",
"decision": "block",
"flags": ["unusual_amount", "new_device", "foreign_ip"],
"transactionId": "t9a8b7c6-..."
}
}

loan_approved

{
"id": "evt_loan_001",
"eventType": "loan_approved",
"tenantId": "550e8400-...",
"timestamp": "2026-02-27T14:22:00Z",
"data": {
"applicationId": "d4c3b2a1-...",
"applicationNumber": "LN20260227001",
"approvedAmount": 500000.00,
"interestRate": 18.5,
"tenureMonths": 12,
"approvalLevel": "branch_manager"
}
}

Retry Behavior

Failed deliveries (non-2xx response) are retried with exponential backoff:

AttemptDelay
11 minute
25 minutes
330 minutes
42 hours
512 hours

After 5 failed attempts, the webhook is marked as failed. Use the X-Webhook-ID header for idempotency — you may receive the same event more than once.

Best Practices

  • Return 200 quickly — Process events asynchronously. Acknowledge receipt immediately, then handle the event in a background job.
  • Handle duplicates — Use X-Webhook-ID to deduplicate. Store processed event IDs and skip duplicates.
  • Verify signatures — Always validate the HMAC signature before processing any event.
  • Monitor failures — Set up alerts for webhook delivery failures and investigate promptly.
  • Use HTTPS — Your webhook endpoint must be served over HTTPS.

Next Steps