Withdrawals have two stages. Funds leave the available balance on submission; the withdrawal must then be confirmed or failed.
Initiating Withdrawals
You execute the on-chain payout: in the merchant_hosted model, you hold the funds. We debit the ledger, reducing the member's balance in our records.
POST /v1/withdrawals ← 扣账(钱立刻从可用扣走)
POST /v1/withdrawals/{id}/confirm ← 你确认链上已出款
POST /v1/withdrawals/{id}/fail ← 你确认出款失败,钱退回
Why the ledger must be debited before the on-chain payout
The member's balance must actually decrease in our ledger before payout. Otherwise, the same funds could be spent again on remittances, conversions, cards or wealth products after they have already been sent on-chain.
State machine: three states, two possible outcomes
┌── confirm ──► settled (终态)
locked ─┤
└── fail ──► failed (终态,钱退回 available)
| State | Where the funds are | Who advances the state |
|---|---|---|
locked | The member.withdrawing bucket | Only you, by calling confirm or fail |
settled | Paid out; merchant.custody decreases accordingly | — Terminal state |
failed | Returned to member.available | — Terminal state |
⚠ There is no third outcome. Funds do not leave
withdrawingautomatically.Without your confirmation, they stay there indefinitely. The member cannot see or spend them,
and our operations team cannot release that bucket using the unfreeze operation.
This is intentional: a normal freeze could be undone by any unfreeze endpoint,
even if the funds had already been paid out on-chain.
Duplicate calls and conflicting outcomes
| Call | Current state | Result |
|---|---|---|
confirm | locked | 200; changes to settled |
confirm | Already settled | 200 + replayed: true, an idempotent replay |
confirm | Already failed | 400 order_not_cancellable |
fail | Already settled | 400 order_not_cancellable |
| Either | Order missing or owned by another merchant | 404 not_found; identical response for both cases |
⚠
faildoes not mean cancel. It means “I have confirmed that no on-chain payout occurred; return the funds.”Calling it before confirming the outcome may return the balance after the funds have already been paid out.
No automatic process can recover that discrepancy.
Destination address
You provide to_address in the request body.
⚠⚠ Known inconsistency: do not design your product around this gap.
This endpoint currently does not consult the address book, enforce the 24-hour cooling-off period or require step-up authentication.
That differs from the intended withdrawal policy that the address book is the only source of destination addresses.
This endpoint may be withdrawn in the future; the product owner has decided to remove member deposits and withdrawals from the merchant-facing interface.
Until then, call
GET /v1/withdraw-addressesfirst, select a registered address for that memberwith
usable=true, and use it asto_address.Do not treat this endpoint as permission to send to arbitrary addresses.
Address book
GET /v1/withdraw-addresses?asset=USDT
POST /v1/withdraw-addresses ← 挂强认证 + 24 小时冷静期
Addresses belong to the member, not to you. In this model, you custody funds but do not take over the member's identity or security controls.
| Field | Key point |
|---|---|
usable | false until the cooling-off period ends |
usable_at | When the address becomes usable |
upstream_valid | 1: provider confirmed validity; -1: provider did not respond (allowed with an audit record) |
⚠ Show addresses that are still in their cooling-off period. Hiding a newly added address makes users think the addition failed,
encouraging them to add it again. Display it disabled, with
usable_at.
⚠ This list is not paginated.
next_cursoris alwaysnullandhas_moreis alwaysfalse.These fields are present only to support a shared pagination component.
⚠ The
networkfilter is case-sensitive and matches exactly. Stored values are uppercase machine-readable codes such asBSC.The
assetparameter, however, is normalized to uppercase. The two parameters follow different rules.
The idempotency key is the only duplicate-debit protection
Unlike deposit reporting, POST /v1/withdrawals with x-idempotency-key has no business transaction reference such as reference.
Retrying with a new key causes another real debit. Use your system's unique identifier for that withdrawal.
This service can be suspended
If the service is disabled, manually suspended or automatically halted because your prepaid balance is below the threshold, POST /v1/withdrawals returns 400 service_unavailable. Requests are not queued: the ledger debit either succeeds immediately or fails immediately.
Check GET /v1/merchant/lines first. A line with halted=true recovers automatically on the next monitoring pass after replenishment. For enabled=false, contact your account manager.
⚠ Orders already in
lockedare unaffected:confirm/failremain available.Suspension prevents new withdrawals; it does not trap existing withdrawals in
withdrawing.
Other possible failures
| Code | Meaning |
|---|---|
asset_not_allowed | Asset is absent from the platform catalog or has been disabled |
insufficient_balance | Member's available balance is insufficient |
limit_exceeded | A limit was exceeded; limit_scope identifies the level |
request_rejected | Member is blocked, blacklisted or frozen |