items has more than one entry.
Bulk withdrawals are available for NGN bank payouts today. A USD stablecoin
withdrawal accepts exactly one item per request; sending more returns
400 VALIDATION_FAILED.How it works
- Build one
itemslist with one entry per beneficiary. - Quote it with
POST /partner/wallets/withdrawal/quotesto preview the total debit and the number of bank transfers. - Submit the same list with
POST /partner/wallets/withdrawalsand oneidempotency_key. - Persist the returned parent
id, then reconcile each transfer withGET /partner/wallets/transactions/{id}.
Limits
The account limit and balance check apply to the whole request, not to each
beneficiary. A request of 50 small payments and a request of one large payment
are both measured by their total. The account limit counts principal only; the
balance check counts principal plus the ₦100 fee per transfer. The item,
transfer, and per-transfer amount limits are server settings that can change,
so treat them as current values rather than permanent constants. Because kobo
is dropped after the request is accepted, send whole-naira amounts; two
requests that differ only in kobo are different intents under the same
idempotency key.
Fees
The NGN withdrawal fee is a flat ₦100.00 for every bank transfer that is created. Most beneficiaries need exactly one transfer, so for a typical bulk request the fee is ₦100.00 multiplied by the number of beneficiaries. A beneficiary above ₦100,000,000.00 is split into more than one transfer and pays the fee for each of them. The quote response reportstransfer_count, the aggregate fee, and the
debit_amount that will leave your NGN wallet, so you can show the true cost
before you submit.
Quote a bulk request
Quote bodies useitems and omit idempotency_key:
300.00, an amount
of 1045000.00, and a debit_amount of 1045300.00. Quoting never reserves
funds, but it runs the account-limit and balance checks and has its own rate
limit, so quote when the operator reviews the list rather than on every
keystroke.
Submit a bulk request
Submit uses the same list and adds oneidempotency_key for the whole batch.
Sign the exact bytes you send, as described in
Request Signing.
idempotency_key per batch, not per beneficiary. Give every item a
client_item_id from your own system. It is optional, but it is the easiest
way to match a transfer outcome back to the right employee, supplier, or
customer, and it comes back in the submit response and in every later
transaction read.
The same bank account can appear more than once in a list. Each entry is paid
separately, so check your list for accidental duplicates before you submit.
Submit response
The response is one parent transaction.items keeps the order of your
request, and each item lists the bank transfers that will pay it.
sequence numbers run across the whole batch, so the first transfer of the
second item continues counting from where the first item stopped. The parent
starts as pending and each transfer as processing. Store data.id
immediately. It is the only identifier you need to follow the batch to
completion.
Outcomes
Each bank transfer succeeds or fails on its own. The parent status summarizes them:
When a transfer fails, its principal and its ₦100.00 fee are returned to your
NGN wallet. Transfers that succeeded stay paid. You never need to reverse or
re-send the successful part of a batch; only re-send the beneficiaries whose
transfers failed, in a new request with a new
idempotency_key.
Each outcome has its own webhook: withdrawal.success, withdrawal.partial,
and withdrawal.failed. Subscribe to all three so a mixed batch is not left
waiting for an event that will never arrive. See
Withdrawal webhook events for the payloads.
A webhook tells you the batch has finished. It does not list who was paid.
Finish bulk reconciliation with the transaction read below, which carries the
per-transfer outcomes.
Reconciling each transfer
Read the parent withGET /partner/wallets/transactions/{id}. The
settlement_details.transfers list carries one entry per bank transfer with
its own status, your client_item_id, and the position of the item in your
original request.
The parent-level
recipient and description show the first beneficiary in
the batch. Use the per-transfer recipient and client_item_id for anything
beneficiary specific. description and recipient on a transfer are omitted
rather than null when absent. On a partial parent, confirmed_at is the
time the batch finished settling and failed_at stays null.
settlement_details.rail_reference carries the same WD- reference as
reference.
In the example above, two employees were paid and one transfer failed. Your
system should mark the first and third employees as paid and queue a fresh
withdrawal for the second employee after checking the account details. The
wallet receives 420100.00 back in total: a refund transaction for the
420000.00 principal appears in the timeline with the description
Refund for failed withdrawal transfers, and the 100.00 fee for that
transfer is released back to the balance alongside it.
Retries and idempotency
A transport failure on submit is handled exactly like a single withdrawal. Keep the same body andidempotency_key, generate a new timestamp and nonce,
sign again, and resubmit. The same key with the same list returns the existing
parent instead of paying anyone twice.
Changing anything in the list under the same key returns
IDEMPOTENCY_CONFLICT. That includes the order of the items, an amount, an
account number, a description, or a client_item_id. If your list changed,
it is a new batch and needs a new key.
Practical tips
- Quote first and show the
transfer_countanddebit_amountto the person approving the batch. - Keep batches at 50 beneficiaries or fewer and split larger payroll runs into
several requests with distinct idempotency keys such as
payroll-2026-09-run-1,payroll-2026-09-run-2. - Save
client_item_idalongside your own record before you submit, so a restart between submit and reconciliation cannot lose the mapping. - Treat
partialas final. Retry only the failed beneficiaries, never the whole batch. - Subscribe to
withdrawal.partialalongsidewithdrawal.successandwithdrawal.failed, or a mixed batch will never trigger your webhook handler. - Reconcile every batch with a transaction read even when a webhook arrived. Webhooks tell you the batch finished; the transaction read tells you who was paid.