active with transferReady: true. See get started with accounts if you do not have them.
1. Resolve a destination
A transfer spends a destination, not an account id.404 destination_account_not_found, the target cannot receive. One code covers every cause, so check the account yourself: status, canCredit, currency, transferReady, and whether it is a customer wallet — wallets are excluded from own_account destinations and must be paid by tag.
2. Preview
"2500000" is ₦25,000 — minor units, as a string. The response comes back in decimals:
totalDebit is the number to show a payer: the amount plus the fee, and what actually leaves the account.
Preview does not check the balance and reserves nothing. It can succeed and the transfer can still fail with
insufficient_funds.3. Transfer
The same body, plus anexternalReference you chose and stored before sending.
completed:
pending instead. That is normal — settlement is asynchronous, and the outcome arrives as a fiat.withdrawal.completed or fiat.withdrawal.failed webhook.
4. Check the ledger
Three things that will bite you
- A failed create still creates a transfer. If execution fails, you get an error and a transfer row in
failedstatus holding yourexternalReference. Retrying with that reference returns the failure, not a new attempt — mint a new reference and resolve a fresh destination. - Destinations are single-use and expire in 24 hours. Resolve one per attempt. Do not store a
destinationIdon a payout row and reuse it. - Approval, if your business uses it, is a dashboard action. A
201withstatus: "awaiting_approval"means the API has done all it can.
If it failed
Next
- Cross-currency transfers
- Use cases — batches, retries, and reconciliation