wallets:withdraw scope when creating a dedicated key, store its signing secret once, and keep it in a server-side secret store.
Production withdrawal submission requires approved KYB for every supported NGN
and USD settlement rail. Withdrawal quotes remain available before approval,
but submit returns 403 while KYB is non-approved. Development and staging skip
this KYB gate.
The signed Partner API request authorizes the withdrawal directly. There is no
emailed verification step and no client-supplied withdrawal token after submit.
See Withdrawal examples for complete Node.js and
Python implementations that quote, sign, submit, and hand completion off to
webhooks.
Routes
This page covers a withdrawal to one beneficiary and the signing, retry, and error rules that apply to every withdrawal. An NGN request can also carry up to 50 beneficiaries; see Bulk withdrawals for the bank-transfer specifics. For NGN withdrawals as CNGN, see the NGN currency guide.
Quote and submit require
X-Api-Key, X-Stabyl-Timestamp, X-Stabyl-Nonce, and X-Stabyl-Signature. Transaction reads require X-Api-Key and wallets:view.
Request Signing
Use the exact bytes sent in the HTTP body. Do not parse and reserialize JSON between hashing and sending the request./v1/partner/wallets/withdrawals, not
/partner/wallets/withdrawals. Include the query string exactly when one is
present.
The server accepts a timestamp up to 30 seconds behind or ahead. A verified nonce is single-use for five minutes. Synchronize server clocks with NTP.
Test Vector
These values are synthetic and must never be used outside tests.Quote NGN
Quote bodies always useitems and omit idempotency_key:
100000000.00 NGN, so a quote for 200000000.00 NGN reports two transfers. The parent amount, fee, and debit remain aggregate values.
Quote Response
Quotes return one entry per settlement route underquotes:
bank_transfer entry. A CNGN quote contains
one CNGN entry. NGN quotes include max_withdrawable_amount and as_of;
an oversized valid request returns status: "unavailable" and a reason_code
with the current maximum. The requested fee, debit, and receive amounts are
preserved. max_transfer_amount is the bank’s per-transfer limit, not the
account’s withdrawable balance. For a beneficiary list, the maximum describes
one destination; availability and fees describe the actual list.
A USD quote contains one entry for the requested settlement_rail; it adds
expires_at and settlement_estimate, omits max_transfer_amount, and can
carry a status other than available with a reason. Submit a USD withdrawal
before its quote expires.
Quoting never reserves funds. Submission rechecks balances and account limits
and can reject a request that was previously available. Quote as part of a user
action rather than in a loop; quotes have their own rate limit.
Submit NGN
Submit uses the same list envelope and adds one parent idempotency key:client_item_id is optional but recommended for correlating your records with response items.
Paying many bank accounts at once? Bulk withdrawals
covers the 50-beneficiary limit, per-transfer fees,
partial outcomes, and
how to reconcile each transfer.Submit USD
USD stablecoin settlement uses the same envelope, with a parentsettlement_rail and exactly one item in the current version:
USDT and USDC on an available chain. A USD quote validates the supplied chain, slug, and destination against the current environment and enabled route before it can return available. Cash USD settlement is not available through this API. An unsupported rail, a destination that does not match the rail, or an unsupported chain returns UNSUPPORTED_WITHDRAWAL_ROUTE; more than one USD item or a missing settlement_rail returns VALIDATION_FAILED.
The USD submit response differs from the NGN one in a few ways: rail is
crypto, settlement_rail is present, amount equals debit_amount with the
fee taken out of it (so estimated_receive is what the destination receives),
transfer_count is 1, max_transfer_amount is absent, and reference
equals id.
Response Shape
The submit response represents one parent transaction.items is always a list, including one-item NGN and USD withdrawals. NGN items can contain multiple transfers. The NGN fee is charged for every generated bank transfer, so fee is the ₦100 flat transfer fee multiplied by transfer_count.
pending and its transfers as processing. reference
is WD- followed by a full UUID; it is generated by Stabyl and is not derived
from id, so store it rather than reconstructing it.
Do not infer completion from the initial response. Persist data.id and wait for
the corresponding withdrawal.success, withdrawal.partial, or
withdrawal.failed webhook. A parent moves from pending to processing
and can remain there while item transfers settle independently; the terminal
statuses are success, partial, failed, and cancelled. withdrawal.partial applies only to NGN parents
with more than one bank transfer, including a single beneficiary whose amount
was split above the per-transfer limit. Use
GET /partner/wallets/transactions/{id} for on-demand confirmation, webhook-gap
recovery, and periodic reconciliation rather than continuous polling.
Safe Retries
For an uncertain HTTP response:- Keep the same body and
idempotency_key. - Generate a new timestamp and nonce.
- Sign the new canonical payload.
- Retry submit, then read the returned parent transaction.
IDEMPOTENCY_CONFLICT. Amounts are compared as
decimals, so 200000000.00 and 200000000 are the same intent.
Errors
Never automatically retry a
400, 401, 402, 403, 409, or 422; correct the request, funding, or key configuration first. Retry 429 and 503 with bounded backoff and a new nonce. Requests need Content-Type: application/json. A body with an unknown field, or one that does not match the request schema, is rejected before the request is processed with a plain-text 422 and no error envelope, so branch on the status code for that case.
For external payouts, subscribe to withdrawal.pending
to receive a best-effort update after the payout is accepted for processing.
Its transaction status is processing; continue waiting for the final
withdrawal.success, withdrawal.partial, or withdrawal.failed event.