Infinity Africa

API Reference

Collections API

Pull a payment from a customer's phone. Four methods, one request shape: push a prompt to their handset (USSD Push, STK Push, or Selcom Pesa Push), or hand them a Dynamic QR code to scan — no phone number needed.

Endpoints

POST/v1/collections/ussd-push

Push a USSD prompt to the customer's phone.

Idempotency-Key required
POST/v1/collections/stk-push

Push an STK (SIM Toolkit) prompt.

Idempotency-Key required
POST/v1/collections/selcom-pesa-push

Push a Selcom Pesa wallet prompt.

Idempotency-Key required
POST/v1/collections/dynamic-qr

Generate a scannable QR code — no phone number required.

Idempotency-Key required
GET/v1/merchants/{merchant_id}/collections

List collections for a merchant.

dashboard
GET/v1/merchants/{merchant_id}/collections/{collection_id}

Get a single collection.

dashboard

Push a collection

The three push endpoints share one request body — customer_phone is required for all three (validated: digits, optional leading +, 9–15 digits). Which endpoint you call is the method — there's no separate method field to set.

json — POST /v1/collections/stk-push
{
  "merchant_id": "5c1f0b2a-3e21-4b9a-9c33-2f6a1d0e8b71",
  "amount": "25000.00",
  "currency": "TZS",
  "customer_phone": "+255712345678",
  "merchant_reference": "ORDER-4821",
  "description": "2 bags of maize flour"
}
json — 202 Accepted
{
  "success": true,
  "data": {
    "id": "9b7e2c1a-...",
    "merchant_id": "5c1f0b2a-3e21-4b9a-9c33-2f6a1d0e8b71",
    "method": "STK_PUSH",
    "amount": "25000.00",
    "currency": "TZS",
    "customer_phone": "+255712345678",
    "merchant_reference": "ORDER-4821",
    "payment_link_id": null,
    "invoice_id": null,
    "status": "processing",
    "provider": "mock_selcom",
    "provider_reference": "MOCK-SELCOM-9F3A1C2B",
    "transaction_reference": "TXN-...",
    "message": "An STK push was sent to the customer's phone — awaiting approval.",
    "expires_at": null,
    "initiated_at": "2026-08-14T09:00:00Z",
    "completed_at": null,
    "created_at": "2026-08-14T09:00:00Z",
    "updated_at": "2026-08-14T09:00:00Z"
  }
}

Generate a Dynamic QR code

/v1/collections/dynamic-qr omits customer_phone entirely and returns two extra fields your app renders as a scannable code:

json — 202 Accepted
{
  "success": true,
  "data": {
    "id": "2d4f8a91-...",
    "method": "DYNAMIC_QR",
    "amount": "15000.00",
    "currency": "TZS",
    "status": "processing",
    "qr_payload": "selcompay://qr?ref=MOCK-SELCOM-7C1E&amount=15000&currency=TZS",
    "qr_expires_at": "2026-08-14T09:05:00Z",
    "qr_image_url": null,
    "expires_at": "2026-08-14T09:05:00Z",
    "message": "Scan the QR code with a mobile money app to complete payment.",
    "...": "same fields as a push collection"
  }
}

qr_image_url is always null today — render qr_payload as a QR code client-side (e.g. with any standard QR-rendering library).

Request fields

FieldTypeNotes
merchant_iduuidYour merchant account ID.
amountdecimal stringMust be greater than 0.
currencystringDefaults to "TZS".
customer_phonestringRequired on all push endpoints; omitted on dynamic-qr.
customer_iduuid, optionalLink this collection to a saved customer.
customer_namestring, optionalShown in the merchant dashboard alongside the collection.
customer_emailstring, optionalShown in the merchant dashboard alongside the collection.
merchant_referencestring, optionalYour own order/reference — max 100 characters.
payment_link_iduuid, optionalCross-validated: must belong to this merchant, be ACTIVE, and accept this method.
invoice_iduuid, optionalLinks this collection to an invoice — marked paid once the collection resolves successfully.
descriptionstring, optionalShown in the merchant dashboard.
callback_urlstring, optionalRecorded alongside the collection; webhooks remain the delivery mechanism (see below).

Resolving a collection

A push or QR collection always comes back status: "processing" — the customer still has to approve it on their phone. It resolves to successful or failed asynchronously — listen for the collection.success / collection.failed webhook, or poll GET .../collections/{collection_id}.

info

Webhooks are the reliable way to resolve a collection

Don't block a customer-facing flow on polling — subscribe to collection.success/collection.failed on the Webhooks page instead.