API Reference
Webhooks
Infinity Africa notifies your server the moment a collection resolves, a payout completes, or an invoice gets paid — so you don't have to poll. Configure your endpoint once; every event after that is pushed to you.
Configuring your endpoint
Set your webhook URL and choose which events to subscribe to from the Merchant Portal's Webhooks page — generate a signing secret there too (shown once, like an API key), and use the page's Send Test Webhook button to confirm your endpoint is reachable before going live. The same page shows the status of your most recent delivery attempt.
/v1/merchant/webhook-configRead your current webhook URL, subscribed events, and whether a secret is set.
MERCHANT_ADMIN, MERCHANT_STAFF/v1/merchant/webhook-configSet the URL/events, and optionally regenerate the signing secret (returned once, in the response).
MERCHANT_ADMIN, DEVELOPER/v1/merchant/webhook-config/testSend a signed sample payload to your configured URL right now and report the result.
MERCHANT_ADMIN, DEVELOPER/v1/merchant/webhook-eventsList your own outbound delivery queue.
MERCHANT_ADMIN, MERCHANT_STAFFEvent types
The events below are the ones your integration should actually handle. A few additional names are reserved in the schema for features on the roadmap (refunds, chargebacks, scheduled sweeps) — you'll see them in the enum, but nothing emits them yet.
| Event | Fires when |
|---|---|
| collection.success | A push or QR collection was confirmed by the customer. |
| collection.failed | A push or QR collection was declined or timed out. |
| disbursement.success | A payout was delivered. |
| disbursement.failed | A payout was declined; its balance reservation was reversed. |
| disbursement.reversed | A previously successful payout was reversed by the provider after settlement. |
| payment_link.paid | A payment link (including one generated from an invoice) was paid. |
| invoice.paid | An invoice reached PAID. |
| collection.pending | Reserved for a future intermediate collection state.(reserved) |
| invoice.overdue | Reserved for a scheduled past-due sweep.(reserved) |
| payment_link.created | Reserved.(reserved) |
| payment_link.expired | Reserved for a scheduled expiry sweep.(reserved) |
| refund.succeeded | Reserved — refunds aren't issued yet.(reserved) |
| refund.failed | Reserved.(reserved) |
| chargeback.opened | Reserved.(reserved) |
| chargeback.resolved | Reserved.(reserved) |
Payload shape
{
"event_name": "collection.success",
"payload": {
"collection_id": "9b7e2c1a-...",
"amount": "25000.00",
"currency": "TZS"
},
"created_at": "2026-08-14T09:00:12Z"
}Verifying a delivery
Every delivery is signed with your merchant's webhook secret, sent as X-Infinity-Signature: an HMAC-SHA256 hex digest of the exact raw request body. Recompute it and compare — don't trust a delivery that doesn't match, and use a constant-time comparison to avoid leaking timing information.
import hashlib
import hmac
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Retries
Every event is recorded to your delivery queue the moment it happens. Automatic retry-with-backoff delivery is on the roadmap but not live yet — for now, use Send Test Webhook on the Portal's Webhooks page to confirm your endpoint responds correctly, and Transaction Status to poll as a fallback. Respond quickly to real deliveries once retries ship — do your processing asynchronously after returning 200, rather than making Infinity Africa wait on it.
Design for at-least-once delivery
payload (e.g. collection_id) and make handling that ID idempotent now, so a duplicate delivery is a safe no-op later.