Guides

Money Movement

Collect one-time payments, send payouts, move money between wallets, and use managed payments when you have no provider account.

Besides invoices and subscriptions, PimaBill moves money on its own: collections (take a one-time payment), payouts (send money out) and transfers (move money between two customer wallets). Each one is a record that goes pending → processing → succeeded or failed, and its outcome is sent to your webhook.

Money movement is off until it is switched on. Collections and transfers can be switched on in Settings → Money; payouts are enabled by the platform operator after a review.

Who pays, who is paid

Collections have a payer and payouts a payee. Both are a party, told apart by type:

typeFieldsUsed for
mobile_moneymsisdn (international format, e.g. 256771234567), optional name, networkPIN prompts and mobile money payouts
bankaccountNumber, bankCode, account nameBank payouts
paypalemailPayPal payouts
yo_accountYo! Payments account numberPayouts to a Yo account
hostedemail, optional nameA checkout page from the provider (card, bank or mobile money)
cryptooptional emailCrypto payment to your wallet address

Collect a payment

A mobile money collection sends a PIN prompt to the payer's phone. The call returns at once with pending; the result arrives on your webhook as collection.succeeded or collection.failed.

curl -X POST https://api.pimabill.com/api/collections \
  -H "Authorization: Bearer sk_live_a1b2c3d4..." \
  -H "Idempotency-Key: order-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "50000",
    "currency": "UGX",
    "payer": { "type": "mobile_money", "msisdn": "256771234567" },
    "reference": "order-1042",
    "description": "Order #1042"
  }'

For a card payment, use a hosted payer: the response carries a paymentUrl to send the payer to, and returnUrl is where they come back to.

Never charge twice. Pass your own reference: reusing it returns the original collection instead of starting another one. The Idempotency-Key header does the same for a retried request.

If you have more than one provider, provider picks one; otherwise the first one that supports the payer type is used. GET /api/money/capabilities lists what each of your providers can do.

Send a payout

import { PimaBillClient } from 'pimabill';

const client = new PimaBillClient({ token: 'sk_live_a1b2c3d4...' });

const payout = await client.payouts.create({
  amount: '120000',
  currency: 'UGX',
  payee: { type: 'mobile_money', msisdn: '256771234567', name: 'Amina N' },
  reference: 'supplier-77',
  description: 'Supplier invoice 77',
  'Idempotency-Key': 'supplier-77',
});

A payout is paid from your provider balance, or from a customer wallet when you pass walletId (the amount is held in the wallet until the payout settles, and released if it fails).

Approval and limits

Set these in Settings → Money, or with PATCH /api/money/settings:

  • Approval threshold per currency: payouts at or above it wait as requires_approval until someone approves them in the dashboard. Approval needs a signed-in person with the money.approve permission; an API key can never approve, so a leaked key cannot both create and approve a payout.
  • Daily limit per currency: the most that can be paid out per UTC day.

Outcomes you cannot see yet

If a provider does not answer, the payout is marked stuck and never sent again automatically: sending it twice could pay twice. Check it with POST /api/payouts/{id}/refresh, which asks the provider for the real outcome.

Move money between wallets

POST /api/transfers moves an amount from one customer wallet to another in the same currency, at once and in one step: both balances change together or not at all.

Webhooks

EventWhen
collection.succeeded, collection.failedThe payer paid, or the payment failed
payout.created, payout.requires_approvalA payout was created, or is waiting for approval
payout.succeeded, payout.failedThe payout arrived, or failed (a wallet hold is released)
collection.stuck, payout.stuckThe provider could not confirm the outcome yet
collection.canceled, payout.canceledIt was canceled before reaching the provider
transfer.succeededMoney moved between two wallets

data is the movement in the same shape the API returns. See Webhooks for signatures and retries.

Managed payments

No payment provider account of your own? Managed payments takes every kind of payment for you: invoice checkouts, subscription renewals, portal payments, payment links and one-time collections. It is part of the cloud plans, including Free, and is not available on self-hosted deployments. Apply in Money movement → Managed payments in the dashboard, or with POST /api/money/managed:

  • Where you are paid. Choose a payout destination: mobile money (with the name on the account, which some providers require), a bank account or PayPal. The platform keeps what it collects for you and pays it out there. Bank details also let it settle each payment straight to your bank where the provider supports it.
  • Changing details. Your email, phone and payout destination can be changed at any time with Edit details on the same card, or PATCH /api/money/managed. Payouts from then on go to the new destination, and every change is recorded by the platform.
  • Review. Your details are reviewed before payments start. The status shows on the same page: in review, active, not approved or suspended. When it changes, the organisation's owners and admins get an email (with the reason, if one was given), and the change shows under the bell in the dashboard.
  • Fees. Managed payments has a small fee per payment, shown on the same card once you are approved. It is taken from each payment before it is paid out to you. Payments through your own providers have no managed payments fee.

Once active, checkouts try your own providers first and use managed payments when none of them can take the payment (for example a revoked key), so you can add your own provider later without changing your integration.

Testing

Use the Sandbox: the same calls with a sk_test_ key move test money only, through each provider's test account.

Copyright © 2026