Baraka Pay DOCS

← Back to docs

Payouts

Sending money out, to a recipient you name. Designed to refuse rather than guess.

A payout optimises for refusal

A failed payment costs you a sale. A wrong payout is money gone, to a party nobody controls, usually irrecoverable. Every ambiguous case in this API refuses, holds, or waits for a person — so if payouts feel more cautious than the payin API, that is the design and not a defect.

Payouts are live in Sandbox today. There is no live rail yet, so a Live deployment has no enabled payout adapter and every payout on it fails closed with provider_unavailable.

Two gates before an app can pay out at all

The payout capability is granted per client and per environment, by Baraka Pay staff, and it is off by default. Absence of a setting means no — every API key that existed before payouts shipped remains unable to send money.

The merchant needs a funded balance. A payout draws on their receipts and there is no overdraft: a payout for more than the available balance is refused and nothing is reserved.

In Live, money that has just arrived cannot be paid straight back out — a payin sits for a holding period first. The merchant loses access to nothing; their balance and settlement are unchanged. It governs only what may fund an irreversible transfer to a third party, because a payin that is paid out and then reversed is money gone somewhere nobody controls. Sandbox sets the hold to zero so you can test a payin and a payout in one sitting; do not assume Live behaves the same.

Creating a payout

Send the amount in minor units, the method, your own reference, and a destination. Use your merchantReference as the Idempotency-Key, verbatim.

201 the first time. 200 with the ORIGINAL payout if that reference has been used before — that difference is the whole defence against a retried withdrawal paying somebody twice, so branch on the status code rather than ignoring it.

POST /v1/payouts Authorization: Bearer sk_test_... Idempotency-Key: BB-WD-1001 { "amount": { "minor": 50000, "currency": "CDF" }, "method": "mpesa", "merchantReference": "BB-WD-1001", "destination": { "identifier": "+243990000000", "holderName": "Amani Kabila", "currency": "CDF" } }

A payout needs an API key, not a portal login

Creating and retrying a payout refuse a merchant-portal session with api_key_required. Reading, listing and cancelling still work with one.

A portal session is a password-only bearer. An API key is a server-side secret. A payout goes wherever the caller says, so it is authorised by the stronger of the two — and a phished portal password does not become the ability to send money. Refunds deliberately still allow a portal session, because a refund returns money to the original payer and cannot be redirected.

Approval is normal, not an error

A payout above the platform's auto-approve threshold — and, by default, every first payout to a destination this merchant has never successfully paid — comes back requires_approval and waits for a member of Baraka Pay staff.

The threshold is one number for the whole deployment, per currency, set by the back office. Payouts at or below it go straight to a rail. A deployment that has configured nothing holds every payout however small, because a platform nobody has configured must not be one that sends money with nobody looking.

The new-destination cool-off is not suspicion of your app. A newly seen destination for an existing recipient is the classic account-takeover payout: the attacker does not raise the amount, they change where the money goes. Design for the wait — "withdrawal submitted, under review" is a normal state.

The destination comes back masked, always

Baraka Pay stores the identifier in full because a rail cannot be handed a masked destination, and returns it to nobody — not to you, not to the staff approval queue, not to the audit trail.

holderName is required and is not decoration. It is the only field that catches a transposed number before the money moves, and where a rail verifies names a mismatch refuses the payout. Send the name the recipient's account actually holds, not the one on your own user record.

payout.returned is the rule integrations forget

Events: payout.pending, payout.approved, payout.processing, payout.succeeded, payout.failed, payout.cancelled, payout.returned. Same signature and same verifier as the payin webhooks — reuse them byte for byte, and re-read the payout rather than trusting the body.

Mobile-money and bank payouts bounce. A closed wallet, a name mismatch caught downstream, a recalled transfer — days after your app told its user the money was sent. The payout moves succeeded to returned, a compensating journal puts the value back, and payout.returned is delivered. Your app must handle a reversal of money it has already credited.

Baraka Pay never converts for a payout: the destination currency must equal the payout currency, and a cross-currency payout is refused rather than converted.