Payouts
Payouts let you send money to a bank account or a mobile money destination — typically for seller settlements, merchant disbursements, or any third-party transfer.
A payout requires a recipient (a registered bank or mobile money destination) and is always processed through a specific gateway configured for your tenant.
All examples in this guide use mygateway to support local/mock testing.
Workflow
There are two ways to create a payout. In both paths you must send a gateway string that identifies the payout integration (e.g. paystack, mygateway). It must match the recipient's gateway for that payout.
Option A — Inline (recommended for most integrations)
Use the single POST /payouts endpoint and provide the bank or mobile money details directly. Orchestrapay will transparently create or reuse a matching recipient before initiating the payout.
- Call
POST /payoutswithgateway, amount, currency, and either a full bank set (bank_account_number,bank_account_name,bank_code) with no mobile fields, or a wallet path with at leastmobile_money_number(addmobile_money_provideronly if your gateway needs it). Same one-of rules as forPOST /payout-recipients. - Store the returned
payout_uuidandrecipient_uuid. - Optionally poll
GET /payouts/{uuid}/statusfor the gateway's live transfer state, or listen for webhooks.
Option B — Two-step
Useful when you want to pre-register recipients and reuse them across multiple payouts (e.g. recurring merchant settlements).
- Register the recipient via
POST /payout-recipients(same bank vs wallet rules) and store therecipient_uuid. - Call
POST /payoutswithgateway,recipient_uuid, amount, currency. - Optionally poll
GET /payouts/{uuid}/statusor listen for webhooks.
Recipient deduplication
When you use Option A (inline details), Orchestrapay computes a deterministic fingerprint: for bank recipients from the tenant, gateway, bank code, and account number; for mobile money from the tenant, gateway, an optional provider segment, and mobile_money_number. Submitting the same destination multiple times reuses the same recipient record — no duplicate registrations for an equivalent fingerprint.
Idempotency Keys
You can provide an idempotency_key on both payout and recipient creation requests. Repeating a request with the same key returns the existing resource rather than creating a new one. We strongly recommend using idempotency keys for all payout requests.
Lifecycle
Payouts follow a promise-based model similar to refunds: a payout may not complete synchronously. Possible statuses are:
| Status | Description |
|---|---|
pending | The payout is being processed by the gateway. |
success | The payout was successfully sent. |
canceled | The payout was rejected or failed. |
Each status has a sub-status that provides more detail. See Payout Sub-statuses below.
Webhooks
Provide webhook_urls and an optional webhook_secret on POST /payouts only (recipients do not have their own webhook URLs). Orchestrapay sends a POST with a JSON body to each URL whenever the payout status changes.
The Orchestrapay-Webhook-Secret header is included on every delivery when you set webhook_secret, so you can authenticate inbound requests.
Typical sequence for a new payout:
pending_created— fired as soon as the payout row is saved, before gateway initiation finishes.pending_promise,success, or acanceled_*sub-status — fired after the gateway responds (or when status is updated later).
Use uuid in the webhook body to correlate with payout_uuid from the create response. The payload also includes recipient_id (internal numeric ID), not recipient_uuid.
See Payout Webhook for field definitions and an example payload. Retry behavior matches other Orchestrapay webhooks (see Webhooks).
Validation errors
You will receive a 400 Bad Request with a human-readable message if:
gatewayis missing, empty, or not a payout-enabled gateway for your environment.- Neither
recipient_uuidnor a valid inline recipient is provided: you must pass either a full bank set (account number, account name, bank code) with no mobile money fields, ormobile_money_numberwith no bank account fields (mobile_money_provideris optional when the gateway does not require it). Partial or mixed groups (e.g. two bank fields only, or both a full bank set andmobile_money_number) are rejected. - The specified
recipient_uuiddoes not belong to the calling tenant. - The gateway does not support payouts or is not registered for payouts.
- The recipient's gateway does not match the requested payout
gateway.
Payout Sub-statuses
Pending
| Sub-status | Description |
|---|---|
pending_created | Payout was just created. |
pending_promise | Payout is queued or in-flight at the gateway. |
Success
| Sub-status | Description |
|---|---|
success | Funds were successfully transferred. |
Canceled
| Sub-status | Description |
|---|---|
canceled_failure | A technical or unexpected error prevented the payout. |
canceled_rejected | The gateway rejected the payout for a documented reason (e.g. invalid account, insufficient balance). |
canceled_timeout | The payout was not completed within the expected window. |