Collections (pay-ins)

A collection asks a customer in a supported country for money. The customer pays in the local currency. After the customer pays, we add the equal amount in USD to your balance. To see the price first, use POST /v1/collections/quote.

Initiate a collection

POST /v1/collections
curl -X POST {BASE_URL}/v1/collections \
  -H "x-api-key: sk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "country": "NG",
    "method": "bank",
    "amount": 50000,
    "customer_id": "cus_123",
    "customer_name": "Ada Obi",
    "reference": "invoice_8842"
  }'
Field Required Description
country yes Corridor country (from corridors).
amount yes The local-currency amount the customer pays. Currency is derived from country.
method no bank or momo. Defaults to the corridor’s default (bank for NG, momo for KE/UG).
customer_name bank, momo The paying customer’s name.
network / phone_number momo Mobile-money network + the customer’s phone number.
customer_id recommended The customer this is for.
reference recommended Your own reference for reconciliation.

Response — bank transfer

For a bank corridor, we give a temporary account in bank_info. The customer sends the money to that account. The amount field shows the USD that we will add to your balance.

{
  "code": 201,
  "message": "Collection initiated",
  "data": {
    "id": "txn_…",
    "type": "deposit",
    "status": "pending",
    "amount": 32.5,
    "currency": "USD",
    "fee": 0,
    "reference": "invoice_8842",
    "description": "NGN Bank Deposit",
    "created_at": "2026-06-25T08:44:25.000Z",
    "bank_info": {
      "bank_name": "Providus Bank",
      "account_name": "Figo / Ada Obi",
      "account_number": "9900112233"
    },
    "timeline": [
      {
        "stage": "requested",
        "message": "We received your request",
        "status": "completed",
        "occurred_at": "2026-06-25T08:44:25.000Z"
      }
    ]
  }
}

Response — mobile money

For mobile-money corridors the customer approves a push prompt on their phone; bank_info is null. Poll the transaction (or use webhooks) for progress.

Response — Latin America

Argentina, Brazil, Colombia and Mexico are bank corridors, and answer in the same bank_info shape — but what fills it differs by country, because the rails do:

Country What the customer is given
Brazil (BR) A Pix code to paste into their banking app
Mexico (MX) A CLABE to send to over SPEI
Colombia (CO) Local bank details, or a link to pay through PSE
Argentina (AR) A CVU to transfer to

Show the customer whatever bank_info carries. Some of these are standing instruments — Colombia's link opens a checkout the customer can pay any amount through, Brazil's Pix code pastes into a banking app — so they may be reused for that customer rather than fetched again per payment.

Crypto collection

Accept a digital asset and get credited USD. First list what customers can pay with and pick a network’s asset_id:

GET /v1/collections/assets
{
  "code": 200,
  "message": "Collection assets fetched successfully",
  "data": [
    {
      "symbol": "USDC",
      "asset_logo": "https://cdn.jsdelivr.net/…/USDC.png",
      "preferred": true,
      "chains": [
        {
          "asset_id": "usdc-polygon",
          "chain": "pol",
          "chain_name": "Polygon",
          "chain_logo": "https://…/POL.png",
          "decimals": 6,
          "price": 0.9999,
          "contract_address": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
          "direct": true,
          "can_deposit": true,
          "can_payout": true,
          "checked_at": "2026-10-05T09:30:00.000Z"
        },
        {
          "asset_id": "usdc-solana",
          "chain": "sol",
          "chain_name": "Solana",
          "chain_logo": "https://…/SOL.png",
          "decimals": 6,
          "price": 0.9999,
          "contract_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "direct": false,
          "can_deposit": true,
          "can_payout": true,
          "checked_at": "2026-10-05T09:30:00.000Z"
        }
      ]
    }
  ]
}

Same shape on all three assets listings. chain is a short code, not the display name. preferred marks the assets we suggest first, and direct: true means we send the asset as-is rather than bridging it.

can_deposit says whether that corridor actually priced the last time we asked, which is the field that matters on a collection — a listed asset is not the same as a priceable one. Absent is not false: it is missing when we have not probed that corridor yet, so filter on === false rather than on falsiness. See payouts for the full field notes.

Then create the collection. We give a deposit address for the customer to use. We also give a memo if the network needs one. The funds settle directly to your balance.

curl -X POST {BASE_URL}/v1/collections \
  -H "x-api-key: sk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "crypto",
    "amount": 100,
    "asset_id": "usdt-polygon",
    "refund_to": "0xCustomerSendingAddress",
    "reference": "invoice_8842"
  }'
Field Required Description
method yes crypto.
amount yes Amount of the chosen asset the customer sends.
asset_id yes A network’s asset_id from GET /v1/collections/assets.
refund_to yes An address the customer can receive at, used if the collection can’t be completed. Not necessarily where they send from — a payment from an exchange cannot be refunded to its sending address.
reference recommended Your own reference for reconciliation.
{
  "code": 201,
  "message": "Collection initiated",
  "data": {
    "method": "crypto",
    "direct": false,
    "pay_to": {
      "address": "G… (or 0x…/T…)",
      "memo": "12345",
      "asset_id": "usdt-polygon",
      "symbol": "USDT",
      "network": "tron",
      "amount": 100,
      "expires_at": "2026-06-25T10:30:00.000Z"
    },
    "amount_usd": 99.5
  }
}

The customer sends the asset to pay_to.address. If we give a memo, the customer must send it with the asset. After the asset settles, we add USD to your balance.

Is the address one-time or permanent? Read direct

This is the field to branch on, and getting it wrong loses money.

direct: false direct: true
The address One-time. Issued for this collection only. Permanent. Your own settlement address, shared by every payment.
expires_at Set. Do not accept payment after it. Absent — it never expires.
Reuse it later? No. Never store it or show it twice. Yes.
Attribution This collection's status tracks the payment. None. We cannot tell this payment from any other.
refund_to Required. Not used.
Conversion The asset is converted; amount_usd is net of fees. 1:1, no fee.

You get direct: true only when the asset the customer is sending is already the asset we settle in, so there is nothing to convert and no temporary address to issue.

A one-time address is exactly that. Funds sent to it after expires_at, or sent to it a second time, are not credited and are not automatically returned — recovering them is a manual support case. If a customer needs to pay again, create a new collection and use the new pay_to.address.

Don't cache the address against your order, don't put it in an email the customer may open days later, and don't render a QR code that outlives expires_at. Re-read it from the collection each time you show it.

A permanent address has no status to poll. Because every payment lands on the same address, nothing distinguishes one from another, so a direct: true collection is never marked paid and no collection.completed fires for it. If you need to know that this invoice was paid, you need a one-time address — which is why a USD-denominated collection (a payment link) refuses a permanent one unless you pass allow_permanent_address: true to say you have no attribution to lose.

Not in sandbox. A crypto collection cannot be completed with test keys — sending an asset to pay_to.address does nothing, because the conversion behind a crypto pay-in runs on mainnet only. The quote and the instructions work; the money cannot arrive, and no webhook fires. Use the settle endpoint below instead.

Testing a collection in sandbox

Nothing arrives on its own with test keys — there is no payer at a bank, and paying the address does not work. You decide when a collection lands:

POST /v1/sandbox/collections/{reference}/settle
{ "outcome": "success" }
{
  "code": 200,
  "message": "Collection settled",
  "data": { "reference": "invoice_8842", "status": "success" }
}

A collection that has already finished cannot be settled as the other outcome. That returns 400 rather than reporting success while leaving it failed.

Everything downstream is real: your balance moves, collection.completed fires at your webhook, and a payment link records the payment. Send {"outcome": "failed"} to rehearse a payment that never arrives — worth doing at least once.

This is the only way to complete a collection in sandbox, whichever method you opened it with. See getting started for the rest of what sandbox does and does not do.

Lifecycle

A collection moves through four stages: wait for payment, payment received, credit to your balance, and settled. The timeline field shows the current stage. See transaction lifecycle.