Skip to main content

External bank payouts

Send same-currency fiat from a customer account to an outside bank (SEPA / ACH / CAD EFT / SWIFT). Source is always a customer-owned fiat account_id (pcus_*). Destination is beneficiary + bank details, not another ledger account_id. Prerequisite: Customers quickstart. Banking gate: products.deposit_account.status=ready.
Sandbox: full payout preview → confirm is often not exercisable end-to-end. Deposit rails may not credit usable fiat balance, and there may be no partner-safe sandbox beneficiary for a real bank push. Prefer validating preview validation (for example amount above available422) and field shapes. Contact Element Pay if you need a sandbox credit or a known-good test beneficiary. Live: fund the source account (deposit instructions or corridor quote → accept where applicable), then run preview → confirm only when you intend to send funds.

Auth and environments

Gates

Source account_id must belong to {customer_id}. If banking routes are called before ready, expect 409 with data such as deposit_account_status and reason: banking_profile_incomplete.

Happy path

1

Confirm banking ready

Poll GET /partner/customers/{customer_id} until deposit_account.status=ready
2

List accounts

Resolve source fiat account_id via GET …/accounts
3

Preview

POST …/payouts/preview with amount, beneficiary, bankpreview_token
4

Confirm

POST …/payouts with preview_token + idempotency_key (tokens expire quickly; use a fresh preview)
5

Poll

GET …/payouts/{payout_id} until terminal status

1. Resolve source account

2. Preview

Preview and confirm must use the same source account_id. Required bank fields depend on currency / scheme (EUR SEPA: iban + country; USD ACH: routing_number + account_number; CAD EFT: institution + transit + account; SWIFT: scheme=swift + swift_bic).
Typical success includes preview_token, amounts, fee fields (often fee_status=pending_confirm until confirm), and customer_id. Save preview_token.
In sandbox, until Element Pay has credited balance and shared a known-good beneficiary, prefer insufficient-balance preview checks (below) over confirm.

3. Confirm

On live, confirm initiates an external bank payout. Do not confirm on live unless you intend to send funds. On sandbox, confirm may still fail or be unavailable without funded balance and an approved test beneficiary.
Reuse the same idempotency_key on retries after network errors. Confirm / GET typically surface final fee status.

4. Get payout

Business rules

  1. Same currency fiat → external bank only (no FX on this rail).
  2. Preview → confirm on the same source account_id; use a fresh preview if the token expired.
  3. Partner-neutral JSON: error message / data do not expose upstream PSP names.
  4. Balance: like book transfers, require available >= amount (exact balance is allowed). This differs from stablecoin sends, which need amount strictly below available (1 minor-unit headroom). Insufficient balance returns 422 with data.available, data.amount, and data.currency when known.
  5. Fees may be pending_confirm on preview and final on confirm/GET. Fees can dominate small principals (for example a fixed fee larger than a tiny payout). Always read preview / confirm / GET fee fields and do not invent a fee schedule; do not assume net receive equals amount.
Practical sandbox check (insufficient only): preview with amount above available422 + balance fields. Do not confirm unless you have usable balance and intend a bank push.

Legacy routes

The same operations exist under /partner/entities/{entity_id}/accounts/.../payouts. Those paths are legacy. New integrations should use pcus_* customer paths only.

Errors

Next