Cash-out: payouts and withdrawals
Send money from your SkanPay balance to a mobile money wallet: to anyone (a payout) or to your own business wallet (a withdrawal).
Payout or withdrawal?
| Payout | Withdrawal | |
|---|---|---|
| Pays | Any MTN or Airtel wallet: customers, suppliers, agents, staff | Only your own approved payout wallets |
| Recipient named by | A phone number (msisdn) | A payout wallet id (destination) |
| Typical use | Marketplace sellers, cash-back, salaries, customer refunds | Taking your earnings out of SkanPay |
| Second approver | Not required | Above a threshold, a second team member must approve |
| Endpoint | POST /v1/payouts | POST /v1/withdrawals |
| Webhooks | payout.successful / payout.failed | withdrawal.successful / withdrawal.failed |
Funding: the available balance
Both are paid from your available balance in the same currency. Money from charges becomes available one day after the charge succeeds; a deposit is available immediately. Check with GET /v1/balances.
The fee is charged on top: the recipient gets amount in full, and your balance is debited total_debited = amount + fee. If the available balance is less than that, the request fails with insufficient_balance and nothing is sent.
The moment a payout or withdrawal is accepted, amount + fee is held from your available balance, so two requests can never spend the same money. If it fails, the whole hold is returned, fee included. If it succeeds, the hold is paid out.
Payouts
Send money to any wallet, by phone number.
curl -X POST https://api.skanpay.website/v1/payouts \
-H "Authorization: Bearer $SKANPAY_SECRET_KEY" \
-H "Idempotency-Key: payout-seller-77-2026-09" \
-H "Content-Type: application/json" \
-d '{
"amount": "150000",
"currency": "UGX",
"msisdn": "+256701234567",
"reference": "payout-seller-77-2026-09",
"narration": "September earnings",
"metadata": { "seller_id": "77" }
}'The response is a payout object. As with charges, 201 means it exists; its status tells you where it is. The outcome arrives as payout.successful or payout.failed. On failure, the full total_debited is back in your available balance.
Withdrawals
A withdrawal moves your own earnings to one of your business's payout wallets. Unlike a payout, it cannot name an arbitrary phone number, so even a leaked API key cannot send your balance to a stranger.
Set up a payout wallet (dashboard only)
- An owner or admin opens Balances → Payout wallets in the dashboard and adds the MTN or Airtel number with a label.
- An owner approves it. If your account has more than one owner, it must be a different owner from the person who added it.
- It then cools off for 24 hours before it can receive money. If a wallet appears that nobody on your team added, revoke it in that window.
API keys can list wallets (GET /v1/payout_destinations) but never add or approve one.
Withdraw
curl -X POST https://api.skanpay.website/v1/withdrawals \
-H "Authorization: Bearer $SKANPAY_SECRET_KEY" \
-H "Idempotency-Key: sweep-2026-09-28" \
-H "Content-Type: application/json" \
-d '{
"amount": "2500000",
"currency": "UGX",
"destination": "wal_01J9Z3K8Q4W6XK2M7N5P0R3T8V",
"reference": "sweep-2026-09-28",
"narration": "Daily sweep"
}'The wallet decides the country and network; the currency must be that country's currency.
Second approval above a threshold
A withdrawal above your account's approval threshold is created but not sent:
{ "id": "wdl_…", "status": "INITIATED", "approval_status": "PENDING", ... }The funds are held immediately. A second team member approves or rejects it in the dashboard; the person who requested it cannot approve it themselves. A withdrawal made with an API key always needs a person to approve it when it is over the threshold.
- Approved:
approval_statusbecomesAPPROVEDand it is sent; you then getwithdrawal.successfulorwithdrawal.failed. - Rejected: it fails with
failure_code: "approval_rejected", the hold is released in full, and you getwithdrawal.failed.
| Currency | Default threshold |
|---|---|
UGX | USh 5,000,000 |
KES | KSh 170,000.00 |
GHS | GH₵ 20,000.00 |
SkanPay can set a different threshold for your account.
Outcomes and failures
Cash-out follows the same statuses and failure codes as cash-in. wallet_not_found, wallet_inactive and wallet_limit_exceeded are the common recipient-side failures. Whatever the reason, a failed payout or withdrawal returns the full hold, fee included, to your available balance.
Errors specific to cash-out
| HTTP | code | Meaning |
|---|---|---|
| 403 | payouts_not_enabled_live | Payouts are not switched on yet. |
| 403 | withdrawals_not_enabled_live | Withdrawals are not switched on yet. |
| 400 | insufficient_balance | Available balance < amount + fee. |
| 400 | destination_not_active | Wallet pending approval, cooling off, or revoked. |
| 404 | destination_not_found | No such payout wallet on your account. |
| 429 | rate_limit_exceeded | More than 60 cash-out requests a minute. |
The phone-number, currency and limit errors from cash-in apply too. The minimum amount is the country minimum, USh 500 in Uganda.