POST /api/v1/disburse

Send money to a mobile money wallet. Live mode only.

✅

Building a full payout flow? Follow the Integration Guide.

Endpoint

output.txtText
POST https://api.rohopay.com/api/v1/disburse

Headers

HeaderRequiredValue
Authorization✅Bearer {live_api_key}
Content-Type✅application/json
Idempotency-Key✅Stable per withdrawal — see Idempotency
⚠️

Disbursements require a live API key and a funded live wallet. Requests with test_ keys return 400 DISBURSEMENT_BLOCKED.

Request Body

response.jsonJSON
{
  "phone": "256700123456",
  "amount": 25000,
  "currency": "UGX",
  "description": "Agent payout – July",
  "callback_url": "https://your-app.com/webhooks/rohopay"
}
FieldTypeRequiredDescription
phonestring✅256XXXXXXXXX — country code, no 0, no +
amountinteger✅Whole currency units (no ×100). Debited from your live wallet
currencystring✅UGX, KES, TZS, or RWF
descriptionstring—Note attached to the payout
callback_urlstring—Public http(s) URL that receives the webhook

How the fee works

Your live wallet is debited exactly amount. The transfer fee is taken out of what is sent to the recipient — it is not charged on top of your debit.

FieldMeaning
wallet_debitWhat was taken from your wallet (amount)
recipient_amountWhat the recipient's phone receives (amount minus the transfer fee)

Both figures are fixed when the payout is created and returned in this response — show recipient_amount to your user. If the recipient must receive an exact figure, add the fee to amount before calling.

Response (200 OK)

response.jsonJSON
{
  "success": true,
  "data": {
    "transaction_id": "01j5m6n7p8q9r0s1t2u3vwxy",
    "reference": "ref_9f8e7d6c5b4a3210",
    "status": "pending",
    "amount": 25000,
    "currency": "UGX",
    "commission": 0,
    "provider": "rohopay",
    "wallet_debit": 25000,
    "recipient_amount": 24100
  },
  "message": "Payment request initiated"
}
⚠️

Check data.status, not just the HTTP code. If the provider rejects the payout immediately, you still get HTTP 200, but status is "failed" and an error_reason explains why. The reserved funds have already been returned to your wallet, and no webhook follows — refund your own user right away.

response.jsonJSON
{
  "success": true,
  "data": {
    "status": "failed",
    "reference": "ref_…",
    "error_reason": "We couldn't reach our payment partner. Your wallet has been refunded — please try again shortly."
  }
}

A pending payout resolves via the webhook (withdraw.successful / withdraw.failed). On failure your wallet is refunded.

One payout at a time

While a payout on your wallet is pending, another disburse returns 409 INFLIGHT_TRANSACTION. Send payouts sequentially and wait for each webhook (or poll) before the next.

Error Responses

HTTPCodeDescription
400MISSING_IDEMPOTENCY_KEYIdempotency-Key header missing
400INVALID_PHONEPhone is not a supported international number
400AMOUNT_TOO_LOW / AMOUNT_TOO_HIGHOutside the allowed range
400DISBURSEMENT_BLOCKEDTest key used — payouts are live only
401INVALID_API_KEYMissing, unknown, or inactive API key
402INSUFFICIENT_BALANCELive wallet balance too low
409INFLIGHT_TRANSACTIONA payout on this wallet is already pending
409CONCURRENCY_CONFLICTWallet changed mid-request — retry as a new attempt
429DAILY_LIMIT_EXCEEDEDDaily disbursement limit reached (resets 00:00 UTC)
429RATE_LIMITEDToo many requests
503DISBURSEMENT_TEMPORARILY_UNAVAILABLENo provider can take this payout right now
502PROVIDER_ERRORThe payout could not be initiated