Webhooks

Webhooks let your systems react to MFI events in real time instead of polling. Configure subscribers per event type with retry policies and verify signatures with a shared secret.

Webhook settings
Webhook subscription + retry config.
At a glance — Transport: HTTPS POST. Format: JSON envelope with event + payload. Signature: X-MFI-Signature header HMAC-SHA256 of body using subscriber secret. Retry: 3 attempts with backoff 1m, 5m, 30m. Dead letter queue after 3 failures.

Event catalogue

EventFires whenPayload
member.createdNew member record committedMember object
member.kyc_completedKYC verification approvedMember + KYC bundle
loan.createdLoan created in draftLoan object
loan.approvedLoan state moves to approvedLoan object
loan.disbursedDisbursement confirmed by channelLoan + disbursement object
loan.repaidRepayment postedLoan + repayment + new balance
loan.delinquentLoan enters PAR>30Loan + days past due
loan.written_offLoan written offLoan object
savings.deposit_postedDeposit bookedAccount + transaction
savings.withdrawal_postedWithdrawal bookedAccount + transaction
aml.alert_raisedNew AML alertAlert + triggering transactions
regulatory.return_lodgedReturn submitted to supervisorReturn object

Event envelope

{
  "id": "evt_01HXY9TZQ7K2P8",
  "event": "loan.disbursed",
  "created": "2026-03-15T10:42:17.182Z",
  "tenant": "microfinance.mybridgeerp.com",
  "api_version": "v1",
  "data": {
    "loan": {
      "id": 9183,
      "loan_no": "L-2026-09183",
      "member_id": 4827,
      "member_name": "Jane Wambui Mwangi",
      "principal": 5000000,
      "state": "active"
    },
    "disbursement": {
      "id": 7211,
      "amount": 5000000,
      "channel": "mpesa_b2c",
      "provider_ref": "PBJ4S6X9DC",
      "posted_at": "2026-03-15T10:42:15Z"
    }
  }
}

Verifying the signature

Every webhook POST carries an X-MFI-Signature header containing an HMAC-SHA256 hex digest of the raw request body, computed with the subscriber secret. Always verify before processing.

import hmac, hashlib

def verify(secret: bytes, body: bytes, signature: str) -> bool:
    expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# Flask example
@app.post('/mfi-webhook')
def receive():
    sig = request.headers.get('X-MFI-Signature', '')
    if not verify(SECRET, request.data, sig):
        abort(401)
    event = request.json
    process(event)
    return '', 200

Delivery and retry

  1. First attempt fires immediately after the source action commits
  2. If subscriber returns non-2xx or times out (default 10s), event queued for retry
  3. Retry attempt 1 after 1 minute
  4. Retry attempt 2 after 5 minutes
  5. Retry attempt 3 after 30 minutes
  6. After 3 failed retries, event moves to dead letter queue in Settings > API > Webhooks > Failed
Warning — Webhooks may arrive out of order. Always idempotent-process by event id; deduplicate on (event id, event type).

Configuring a subscriber

Settings > Technical > Webhooks > Create.

  • Name — human label
  • URL — HTTPS endpoint
  • Secret — generated once, never shown again
  • Events — multi-select event types to subscribe to
  • Active — toggle to disable without deleting
  • Headers — optional custom headers (e.g. Authorization)

Worked scenarios

Scenario — SMS reminders via webhook

Setting: Mwananchi SACCO wires a webhook subscriber to push SMS reminders to members when a loan goes delinquent.

CharacterRole
Mary MutuaCustomer Communications Lead
Samuel KibetDevOps Engineer
Jane WambuiBorrower

Timeline

  1. Day 1, 10:00: Samuel creates webhook subscriber. (URL https://sms-bridge.mwananchi.co.ke/inbound, events loan.delinquent.)
  2. Day 1, 11:00: Samuel deploys SMS bridge service. (Bridge calls Africa's Talking SMS API on each event.)
  3. Day 31, 09:00: Engine detects Jane's loan at 31 DPD. (loan.delinquent event fires.)
  4. Day 31, 09:00:02: SMS bridge receives webhook, verifies HMAC. (Signature ok; bridge invokes Africa's Talking.)
  5. Day 31, 09:00:05: Jane's phone vibrates. ('Dear Jane, your loan L-2026-09183 is 31 days overdue. Please pay KES 18,540 today to avoid CRB listing. Mwananchi SACCO.')
  6. Day 31, 14:00: Jane pays via M-Pesa Paybill. (Repayment posted; loan.repaid event fires.)
  7. Day 31, 14:00:02: SMS bridge sends thank-you. ('Thank you Jane. KES 18,540 received. Your loan is now current.')

Outcome — SMS reminders fully automated without polling. Mary tracks effectiveness via the webhook delivery dashboard and SMS delivery reports.

Reference

Webhook request headers

HeaderValuePurpose
Content-Typeapplication/jsonAlways JSON
X-MFI-EventConvenience copy of envelope event
X-MFI-Event-IdConvenience copy of envelope id
X-MFI-SignatureHMAC-SHA256 of body
X-MFI-Delivery-Attempt1, 2, 3, or 4Retry counter (4 = dead letter)
X-MFI-TenantSource tenant
User-AgentMFI-Webhooks/1.0Identification

Troubleshooting

SymptomLikely causeFix
All webhooks failing with 401 from subscriber.Subscriber's secret rotated but MFI not updated.Settings > Webhooks > Edit > regenerate or paste new secret.
Webhooks arriving out of order causing duplicate processing.Subscriber not deduplicating on event id.Persist event id on receipt; ignore if seen.
Subscriber dead letter queue filling up overnight.Subscriber down or rate-limiting.Investigate subscriber; once recovered, click 'Replay all' on dead letter queue.
loan.delinquent never fires though loans clearly overdue.Daily ageing cron disabled.Settings > Technical > Scheduled Actions > MFI Loan Ageing > enable.

See also

Was this page helpful?