Skip to main content
Money arrives over three rails, and the API models each one honestly rather than pretending they’re the same. If you’d rather not choose, or want all three on one page, use payment links and let the payer pick.

Mobile money charge

You know the payer’s number and the amount. You ask; they approve on their phone.
The collection is pending while the prompt sits on their phone; it becomes completed (balance credited, collection.completed fires) or failed (declined, timed out, wallet error: collection.failed). A timeout is normal: people put their phone down. Let them retry.

Virtual account

A real bank account number that belongs to your organization. Give it to a customer; whatever they transfer credits your balance.
Create one per customer and store the mapping. The externalReference is yours, so you can tell later whose money arrived. Each inbound transfer fires account.credited and shows up as a collection. Pause an account with PATCH … { "status": "paused" }. This is the rail that makes Nigeria work, and the one to reach for whenever the payer, not you, decides the amount.

Ghana (GHS): set a representative first

GHS virtual accounts are real accounts at UMB, and the bank opens them against a named Ghanaian identity: your organization representative. Set it once, as an owner or admin, before your first GHS account: in the dashboard (Add funds → Bank transfer in GHS asks for it) or with a team-member session:
After that, POST /v1/virtual-accounts { "currency": "GHS" } works with any key that has collections:write. Before it, the call returns 422 naming the missing representative, so check GET /v1/organization/representative (null until set) if your integration provisions GHS accounts on demand. In sandbox you can skip this. Without a representative, sandbox uses your organization owner’s name (the name you signed up with), with placeholder phone and ID. GET /v1/organization/representative returns it with status: "sandbox_default", and GHS accounts open straight away with that name as accountName. Setting a representative replaces the default. Production never fills one in, so set a real one before you go live.
  • API keys can read the representative but not set it (403). It is a person’s identity, so it stays with the humans on your team.
  • Every GHS account’s accountName is the representative’s name as the bank holds it, which is the name your customer sees in their bank app before confirming the transfer.
  • The bank registers the representative when your first GHS account is opened. Editing fullName or idNumber afterwards doesn’t reach the bank; email api-support@chippercash.com if your representative actually changes.
  • GHS accounts are one-per-customer only. Hosted checkout doesn’t offer bank transfer in GHS; Ghanaian payers use mobile money there.

Crypto address

A deposit address for one asset on one chain.
Creating the same (currency, chain) pair again returns the existing address. Deposits are detected on-chain, wait for confirmations, then credit your balance in the deposit’s asset, converted at the live rate if your balance is fiat. The assets and chains available on each stack come from GET /v1/capabilities → deposits (testnets on sandbox). Tell your customer the asset and the chain. USDC sent on the wrong network to a Solana address is gone.

Funding your own balance

The same two mechanisms are how you top up in production: create a virtual account or a crypto address in your own name and transfer to it, or use Add funds in the dashboard, which does exactly that.

What “received” means

All three end the same way: a ledger credit on your balance (available rises), an account.credited webhook, and a row in GET /v1/transactions. Reconcile on externalReference where you set one, or on the virtual account / address id where the payer chose the amount.