Skip to main content
Prefer webhooks over polling. After you create checkout or move a hold, wait for the event. Do not hammer GET /escrows/{id}.
This is the product, end to end. You are the merchant. A payer is paying. A payee is receiving on release. Amounts are naira integers. Money in a hold is ours to move by API, not the customer’s bank. Release credits the payee’s 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 pass checkout: 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 is disputed, you cannot release or refund until you resolve. If it is awaiting_funding, you cannot release or refund. Cancel or wait for funding.