InfinityPay

API Reference

Collections API

Three ways to collect a payment, all backed by the same 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://businessestore.co.tz/thank-you",
  "cancel_url": "https://businessestore.co.tz/payment-failed"
}
json — 202 Accepted
{
  "success": true,
  "data": {
    "collection_id": "9b7e2c1a-...",
    "reference": "ORDER-4821",
    "status": "created",
    "payment_url": "https://infinitypay.me/pay/8f3a1c2b"
  }
}

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 dashboard'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, the carrier is detected automatically), /selcom-pesa to push specifically to a Selcom Pesa wallet. phone is required for both — a push has nowhere to go without one.

Any business or service provider can collect this way. One call to /wallet-push reaches every supported wallet and bank — you do not need a separate integration, endpoint or credential per network, and you do not need to ask the customer which one they use. Send the phone number and the carrier is resolved for you:

M-PesaTigo PesaAirtel MoneyHaloPesaSelcom PesaCRDB BankNMB Bank

Typical integrations: an e-commerce checkout pushing a prompt when the customer clicks Pay, a utility or subscription service collecting a recurring bill, an ISP or property manager billing tenants, or a point-of-sale system taking payment in person. The flow is identical in every case — push, wait for the webhook, then fulfil.

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

A successful wallet push only means the prompt was accepted for delivery. 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 the payment provider's 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 provider-returned qr value>",
    "expires_at": null
  }
}

expires_at is always null today — the provider'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 the payment provider 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 business 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.

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.