API Reference

Collections API

Three ways to collect a payment, all backed by the same real Selcom Checkout integration and the same reversal-safe crediting lifecycle: hand the customer an Infinity Payment Page, push a prompt straight to their phone, or hand them a QR code to scan.

Which flow should I use?

Infinity Payment Page

Recommended for ecommerce websites, mobile apps, and invoices. You don't pick a payment method — Infinity hosts a page where the customer chooses Mobile Money Push, Selcom Pesa, or Scan QR themselves.

Direct Wallet Push / Selcom Pesa

Best when you already have the customer's phone number and want the fastest checkout — sends a real prompt immediately, no redirect.

Scan QR / TanQR

Best for POS/counter payments, delivery, or any screen where a customer scans instead of typing a phone number.

Endpoints

POST/v1/collections

Create an Infinity Payment Page — returns payment_url.

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

Send a Mobile Money Push prompt immediately.

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

Send a Selcom Pesa prompt immediately.

Idempotency-Key required
POST/v1/collections/qr

Create a Scan QR / TanQR collection.

Idempotency-Key required
GET/v1/collections/{collection_id}

Check a collection's current status.

API key or dashboard
POST/v1/collections/{collection_id}/refresh-status

Force a fresh check with the provider.

API key or dashboard

Every collection_id above works with both status endpoints, regardless of which of the four creation endpoints returned it.

1. Infinity Payment Page

Create a collection, redirect your customer to the returned payment_url, and let Infinity handle the rest. Your backend never sees a phone number, a QR code, or a "method" field — the customer picks Mobile Money Push, Selcom Pesa, or Scan QR / TanQR on that page.

  1. Customer checks out on your website/app.
  2. Your backend calls POST /v1/collections.
  3. Infinity returns payment_url.
  4. Redirect the customer there.
  5. Customer chooses a payment method and pays.
  6. Infinity sends a webhook when the status changes (see the Webhooks page).
  7. Mark the order paid only once you see collection.successful.
json — POST /v1/collections
{
  "merchant_id": "5c1f0b2a-3e21-4b9a-9c33-2f6a1d0e8b71",
  "amount": 50000,
  "currency": "TZS",
  "customer_name": "Grace Mwakalinga",
  "customer_phone": "255712345678",
  "customer_email": "grace@example.com",
  "reference": "ORDER-4821",
  "description": "Payment for order ORDER-4821",
  "redirect_url": "https://merchantstore.co.tz/thank-you",
  "cancel_url": "https://merchantstore.co.tz/payment-failed"
}
json — 202 Accepted
{
  "success": true,
  "data": {
    "collection_id": "9b7e2c1a-...",
    "reference": "ORDER-4821",
    "status": "created",
    "payment_url": "https://infinityafrica.net/pay/8f3a1c2b"
  }
}
warning

webhook_url is not per-request

The webhook_url field is accepted for forward compatibility but not yet used — configure your webhook URL once for your whole account via PATCH /v1/merchant/webhook-config or the Merchant Portal's Webhooks page. See the Webhooks page for details.

2. Direct Wallet Push / Selcom Pesa

Same shape, two endpoints — /wallet-push for a general Mobile Money Push (STK/USSD, Selcom auto-detects the customer's carrier), /selcom-pesa to push specifically to a Selcom Pesa wallet. phone is required for both — a push has nowhere to go without one.

warning

A 202/"processing" response means the prompt was sent — nothing more

Wallet push and Selcom Pesa push success only mean Selcom accepted the push request. It does not mean the customer approved it or that funds moved. Never mark an order paid from this response — wait for collection.successful (webhook) or poll GET /v1/collections/{collection_id} until status is successful.
json — POST /v1/collections/wallet-push
{
  "merchant_id": "5c1f0b2a-3e21-4b9a-9c33-2f6a1d0e8b71",
  "amount": 50000,
  "currency": "TZS",
  "phone": "255712345678",
  "customer_name": "Grace Mwakalinga",
  "reference": "ORDER-4821",
  "description": "Payment for order ORDER-4821"
}
json — 202 Accepted
{
  "success": true,
  "data": {
    "collection_id": "9b7e2c1a-...",
    "reference": "ORDER-4821",
    "status": "processing",
    "message": "Payment prompt sent. Please approve on your phone."
  }
}

POST /v1/collections/selcom-pesa takes and returns the exact same shape — only the prompt message differs ("Selcom Pesa prompt sent. Please approve in your Selcom Pesa app.").

3. Scan QR / TanQR

customer_phone is optional here — nothing gets pushed to it. qr_payload and payment_token are exactly what Selcom's own order-creation response returned: Infinity never generates its own payment QR. Render qr_payload as a scannable code client-side (any standard QR library) exactly as received — don't re-encode it, and don't build your own payload from the order details.

json — POST /v1/collections/qr
{
  "merchant_id": "5c1f0b2a-3e21-4b9a-9c33-2f6a1d0e8b71",
  "amount": 50000,
  "currency": "TZS",
  "customer_name": "Grace Mwakalinga",
  "reference": "ORDER-4821",
  "description": "Counter payment"
}
json — 202 Accepted
{
  "success": true,
  "data": {
    "collection_id": "9b7e2c1a-...",
    "reference": "ORDER-4821",
    "status": "processing",
    "payment_token": "80008000",
    "qr_payload": "<exact Selcom-returned qr value>",
    "expires_at": null
  }
}

expires_at is always null today — Selcom's order-creation response doesn't include a QR/token expiry field, so this is never fabricated. Don't assume the code is time-limited unless a future response actually returns one.

Checking status

json — GET /v1/collections/{collection_id}?merchant_id=... — 200 OK
{
  "success": true,
  "data": {
    "collection_id": "9b7e2c1a-...",
    "reference": "ORDER-4821",
    "status": "pending_clearance",
    "amount": 50000,
    "currency": "TZS",
    "method": "wallet_push",
    "provider_payment_status": "COMPLETED",
    "created_at": "2026-08-24T09:00:00+03:00",
    "updated_at": "2026-08-24T09:02:00+03:00"
  }
}

POST /v1/collections/{collection_id}/refresh-status forces a fresh check with Selcom instead of waiting for a webhook — safe to call repeatedly. It never double-credits or double-reverses, and is a no-op if the customer hasn't picked a method yet on an Infinity Payment Page collection.

Status lifecycle

See the Webhooks page for the full lifecycle table and the merchant order-payment rule. In short: only mark an order paid when status is successful — never from created, processing, a QR/token being returned, or a wallet-push resultcode of 000.

info

Webhooks are the reliable way to resolve a collection

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