available. A bank payout is a later POST /withdrawals.
1. Create customers
POST /customers for the payee (and for a registered payer). You get identity, walletId, and zero balances. account is null. No webhook.
The payer may instead be a guest on create. Pass guest: { email, name?, phone? } on the payer party. No Demat customer or wallet is created for guests.
Look up NIN/BVN with POST /identity/lookups if you need the name. We do not store KYC on the customer.
2. Create the hold
POST /escrows with your unique reference, amount, currency, and parties: one payer, one payee. Payee must use customerId. Payer uses customerId or guest, never both.
Default status is awaiting_funding. Nothing is locked. Webhook: escrow.created.
debitPayer: true locks a registered payer’s available now. Not allowed for guest payers. They must already have enough or the request fails 422 and no hold is created. Success: escrow.created then escrow.funded. Do not also pass checkout: true.
Pass checkout: true on the same create to also return a checkout URL (uses the payer’s email, including guest email).
A transfer into a NUBAN never locks this hold. To lock that money later, create a new hold with debitPayer: true.
3. Fund it (if you did not debit)
If you did not passcheckout: true on create, POST /checkout/sessions with escrowId. Amount and currency must match. The payer opens checkoutUrl (card, bank, bank_transfer, ussd).
A second create for the same unfunded hold returns the same pending session. Use this to retry a failed checkout or start one later. List sessions with GET /checkout/sessions.
Wait for webhooks, not the browser callback. Paid and hold still awaiting leads to payment.succeeded then escrow.funded (status held). Paid after you cancelled leads to wallet.credited on the payer; the hold stays cancelled.
4. After it is held
Omit
amount on release or refund to move everything still held. Pass a smaller amount to leave the rest. You can release 250 then refund 150.
Release is not a bank transfer. Withdraw from available later.
5. Pay out to a bank
POST /withdrawals with customerId, accountNumber, bankCode, amount, and reference. We resolve the account and debit that customer’s available. Listen for withdrawal.completed or withdrawal.failed. Optional: preview the name first with POST /identity/bank-accounts/verify.
Status cheat sheet
Escrow, dispute, checkout, and withdrawal statuses are listed on the escrows, checkout, and payouts tags in the API Reference. Rule of thumb: if the hold isdisputed, you cannot release or refund until you resolve. If it is awaiting_funding, you cannot release or refund. Cancel or wait for funding.