Money Movement
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:
type | Fields | Used for |
|---|---|---|
mobile_money | msisdn (international format, e.g. 256771234567), optional name, network | PIN prompts and mobile money payouts |
bank | accountNumber, bankCode, account name | Bank payouts |
paypal | email | PayPal payouts |
yo_account | Yo! Payments account number | Payouts to a Yo account |
hosted | email, optional name | A checkout page from the provider (card, bank or mobile money) |
crypto | optional email | Crypto 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"
}'
import { PimaBillClient } from 'pimabill';
const client = new PimaBillClient({ token: 'sk_live_a1b2c3d4...' });
const collection = await client.collections.create({
amount: '50000',
currency: 'UGX',
payer: { type: 'mobile_money', msisdn: '256771234567' },
reference: 'order-1042',
description: 'Order #1042',
'Idempotency-Key': 'order-1042',
});
console.log(collection.status); // "pending" until the payer approves
from pimabill import Party_MobileMoney, PimaBillClient
client = PimaBillClient(token="sk_live_a1b2c3d4...")
collection = client.collections.create(
amount="50000",
currency="UGX",
payer=Party_MobileMoney(msisdn="256771234567"),
reference="order-1042",
description="Order #1042",
idempotency_key="order-1042",
)
print(collection.status) # "pending" until the payer approves
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',
});
from pimabill import Party_MobileMoney, PimaBillClient
client = PimaBillClient(token="sk_live_a1b2c3d4...")
payout = client.payouts.create(
amount="120000",
currency="UGX",
payee=Party_MobileMoney(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_approvaluntil someone approves them in the dashboard. Approval needs a signed-in person with themoney.approvepermission; 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
| Event | When |
|---|---|
collection.succeeded, collection.failed | The payer paid, or the payment failed |
payout.created, payout.requires_approval | A payout was created, or is waiting for approval |
payout.succeeded, payout.failed | The payout arrived, or failed (a wallet hold is released) |
collection.stuck, payout.stuck | The provider could not confirm the outcome yet |
collection.canceled, payout.canceled | It was canceled before reaching the provider |
transfer.succeeded | Money 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.