Skip to main content
Version: 202502

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.

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.

  1. Call POST /payouts with gateway, 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 least mobile_money_number (add mobile_money_provider only if your gateway needs it). Same one-of rules as for POST /payout-recipients.
  2. Store the returned payout_uuid and recipient_uuid.
  3. Optionally poll GET /payouts/{uuid}/status for 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).

  1. Register the recipient via POST /payout-recipients (same bank vs wallet rules) and store the recipient_uuid.
  2. Call POST /payouts with gateway, recipient_uuid, amount, currency.
  3. Optionally poll GET /payouts/{uuid}/status or 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:

StatusDescription
pendingThe payout is being processed by the gateway.
successThe payout was successfully sent.
canceledThe 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:

  1. pending_created — fired as soon as the payout row is saved, before gateway initiation finishes.
  2. pending_promise, success, or a canceled_* 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:

  • gateway is missing, empty, or not a payout-enabled gateway for your environment.
  • Neither recipient_uuid nor 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, or mobile_money_number with no bank account fields (mobile_money_provider is optional when the gateway does not require it). Partial or mixed groups (e.g. two bank fields only, or both a full bank set and mobile_money_number) are rejected.
  • The specified recipient_uuid does 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-statusDescription
pending_createdPayout was just created.
pending_promisePayout is queued or in-flight at the gateway.

Success

Sub-statusDescription
successFunds were successfully transferred.

Canceled

Sub-statusDescription
canceled_failureA technical or unexpected error prevented the payout.
canceled_rejectedThe gateway rejected the payout for a documented reason (e.g. invalid account, insufficient balance).
canceled_timeoutThe payout was not completed within the expected window.