Skip to main content
Withdrawal access is disabled on new API keys by default. Enable the 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

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.
Join those six values with newline characters and no trailing newline. Sign the UTF-8 bytes with Ed25519, then encode the 64-byte signature as unpadded base64url. Sign the complete path exactly as it appears in the request URL. For the production submit URL, sign /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.
The exact compact body for that vector is:

Quote NGN

Quote bodies always use items and omit idempotency_key:
A quote for 200000000.00 NGN reports two transfers because no individual bank transfer exceeds 100000000.00 NGN. The parent amount, fee, and debit remain aggregate values.

Submit NGN

Submit uses the same list envelope and adds one parent idempotency key:
You can send multiple beneficiaries in one request. client_item_id is optional but recommended for correlating your records with response items.

Submit USD

USD stablecoin settlement uses the same envelope, with a parent settlement_rail and exactly one item in the current version:
Supported settlement rails are 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; unsupported combinations return UNSUPPORTED_WITHDRAWAL_ROUTE.

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.
Do not infer completion from the initial response. Persist data.id and wait for the corresponding withdrawal.success or withdrawal.failed webhook. A parent can remain processing while item transfers settle independently. 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:
  1. Keep the same body and idempotency_key.
  2. Generate a new timestamp and nonce.
  3. Sign the new canonical payload.
  4. Retry submit, then read the returned parent transaction.
The idempotency-key namespace is shared across every withdrawal currency and settlement rail for the account. The same key with the same normalized intent returns the existing parent. Reusing it for USD after NGN, for NGN after USD, or with a changed amount, destination, description, item order, client item value, currency, or settlement rail returns IDEMPOTENCY_CONFLICT.

Errors

Never automatically retry MISSING_SCOPE, INVALID_SIGNATURE, IDEMPOTENCY_CONFLICT, or UNSUPPORTED_WITHDRAWAL_ROUTE. Correct the request or key configuration first.