Infinity Africa

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.

GET/v1/merchant/webhook-config

Read your current webhook URL, subscribed events, and whether a secret is set.

MERCHANT_ADMIN, MERCHANT_STAFF
PATCH/v1/merchant/webhook-config

Set the URL/events, and optionally regenerate the signing secret (returned once, in the response).

MERCHANT_ADMIN, DEVELOPER
POST/v1/merchant/webhook-config/test

Send a signed sample payload to your configured URL right now and report the result.

MERCHANT_ADMIN, DEVELOPER
GET/v1/merchant/webhook-events

List your own outbound delivery queue.

MERCHANT_ADMIN, MERCHANT_STAFF

Event 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.

EventFires when
collection.successA push or QR collection was confirmed by the customer.
collection.failedA push or QR collection was declined or timed out.
disbursement.successA payout was delivered.
disbursement.failedA payout was declined; its balance reservation was reversed.
disbursement.reversedA previously successful payout was reversed by the provider after settlement.
payment_link.paidA payment link (including one generated from an invoice) was paid.
invoice.paidAn invoice reached PAID.
collection.pendingReserved for a future intermediate collection state.(reserved)
invoice.overdueReserved for a scheduled past-due sweep.(reserved)
payment_link.createdReserved.(reserved)
payment_link.expiredReserved for a scheduled expiry sweep.(reserved)
refund.succeededReserved — refunds aren't issued yet.(reserved)
refund.failedReserved.(reserved)
chargeback.openedReserved.(reserved)
chargeback.resolvedReserved.(reserved)

Payload shape

json — POST to your webhook_url
{
  "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.

python
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.

info

Design for at-least-once delivery

Once automatic retries are live, treat every delivery as at-least-once, not exactly-once. Key your own processing off the resource ID inside payload (e.g. collection_id) and make handling that ID idempotent now, so a duplicate delivery is a safe no-op later.