Withdrawal lifecycle
- Preview — check available balance and get a sourcing estimate.
- Initiate — create the withdrawal. The system reserves liquidity and begins sourcing.
- Track — poll or subscribe to webhooks as the withdrawal progresses.
- Complete — the withdrawal reaches
completedwhen all payout legs are terminal.
Preview a withdrawal
| Field | Description |
|---|---|
amountUsd | Requested withdrawal amount |
feeUsd | Fee charged (currently "0.00") |
availableUsd | Unreserved balance in the relevant bridge domain(s) |
estimatedCompletionTime | Best-effort ISO 8601 duration for end-to-end completion |
Initiate a withdrawal
| Field | Required | Description |
|---|---|---|
requestId | Yes | UUID v4 idempotency key |
chain | Yes | Destination chain |
token | Yes | Destination token |
withdrawalAmount | Yes | Amount as a string (e.g. "65000", "100.50") |
destinationAddress | Yes | Recipient address on the destination chain |
Field naming
The preview endpoint usesdestinationChain / destinationToken (read-only query describing where you’d send). The initiate endpoint uses chain / token (action parameters). This is intentional — the preview scopes availability; the initiate commits the withdrawal.
Amount limits
- Must be a positive number.
- Max 6 decimal places for USDC (e.g.
"100.123456"is valid,"100.1234567"returnsprecision_overflow). - Cannot exceed the wallet’s unreserved balance. Use the preview endpoint to check availability.
- There is no minimum withdrawal amount, but very small withdrawals may not be economically efficient due to gas and bridging costs.
Sandbox notes
chain: "ethereum"refers to Ethereum Sepolia in sandbox.- Solana is part of the CCTP unified bridge domain for sourcing liquidity, but direct Solana withdrawal destinations are not yet supported. The endpoint currently expects an EVM destination address.
Withdrawal response
Top-level fields
| Field | Description |
|---|---|
id | Unique withdrawal identifier |
requestId | Your idempotency key |
walletId | Parent wallet ID |
amountUsd | Withdrawal amount as a USD string |
feeUsd | Fee charged (currently "0.00") |
destinationChain | Target chain |
destinationToken | Target token |
status | Current status (see Withdrawal statuses) |
txHash | Primary delivery tx hash (populated when payout is broadcast) |
failureReason | Machine-readable reason when status is failed (see Failure reasons) |
createdAt / updatedAt | Timestamps |
completedAt | When the withdrawal reached terminal state (null if in progress) |
payouts[] — delivery legs
Each withdrawal has one or more payout legs tracking chain-level delivery.
| Field | Description |
|---|---|
id | Payout leg identifier |
legKey | Unique key in the format pos_{positionKey}_{attempt} (e.g. pos_usdz_001). The attempt suffix is zero-padded and increments on retry. |
status | Payout leg status (see Payout leg statuses) |
amount | Amount as a USD string |
token | Token being delivered |
chain | Chain for delivery |
destinationAddress | Recipient address |
txHash | Onchain tx hash (populated after broadcast) |
sourceLegs[] — liquidity sourcing
Source legs show where funds are being unwound from to fulfill the withdrawal.
| Field | Description |
|---|---|
id | Source leg identifier |
positionKey | Which position is being sourced (e.g. usdz, cash_usdc:cctp) |
sourceChain | Chain where the position is held |
requestedUsdcUnits | Amount in USDC base units (6 decimals). "35000000000" = 35,000 USDC. Divide by 1,000,000 to get USDC. |
status | Source leg status |
Withdrawal statuses
| Status | Terminal | Meaning |
|---|---|---|
pending | No | Accepted and reserved; sourcing hasn’t started |
processing | No | Unwinding, bridging, or delivering in progress |
completed | Yes | All payout legs finished successfully |
partially_completed | Yes | Some payout legs completed, others failed or were cancelled |
failed | Yes | Withdrawal failed |
cancelled | Yes | Withdrawal was cancelled before any payouts completed |
Payout leg statuses
| Status | Terminal | Meaning |
|---|---|---|
planning | No | Leg is being planned |
waiting_for_prior_approval | No | Blocked behind a prior approval on the same wallet+chain |
pending_customer_approval | No | Turnkey approval required |
pending_broadcast | No | Signed, waiting to broadcast |
broadcasted | No | Broadcast submitted, waiting for finality |
processing | No | In progress |
completed | Yes | Delivered successfully |
failed | Yes | Failed |
cancelled | Yes | Cancelled |
skipped | Yes | Leg not needed (e.g. no unwind required) |
Failure reasons
When a withdrawal or payout leg fails,failureReason contains a machine-readable code:
| Reason | Meaning |
|---|---|
cancelled | Cancelled by system |
cancelled_by_customer | Cancelled by customer request |
usdc_authorization_expired | USDC transfer authorization expired before broadcast |
onchain_reverted | EVM transaction reverted onchain |
onchain_failed | Solana transaction failed onchain |
turnkey_activity_failed | Turnkey signing activity failed |
cash_leg_unfulfillable_stale_ledger | Cash source insufficient (stale balance data) |
Customer approvals (head-of-line gating)
Only one customer approval can be outstanding perwalletId + chain. If you submit multiple withdrawals quickly, later ones queue behind the earlier approval.
Withdrawal responses include:
customerApprovalState:"required"|"queued"|"not_required"blockedBy: describes the blocking payout leg when queued
List withdrawals
| Param | Default | Description |
|---|---|---|
limit | 25 | Items per page (1–100) |
cursor | — | Opaque cursor from nextCursor |
sort | createdAt | Sort field |
order | desc | asc or desc |
status | — | Repeatable filter (e.g. status=processing&status=completed) |
Fetch a withdrawal
Cancel a withdrawal
partially_completed rather than cancelled.
You can also cancel or retry individual payout legs:
Timing expectations
| Position | Unwind time | Notes |
|---|---|---|
| Cash (USDC) | Instant | No unwind needed |
usdz | Instant (PT0H) | Convert to USDC on Arbitrum |
syrupUsdc | Instant (PT0H) | ERC-4626 redemption on Ethereum |
rlp | Up to 24h (PT24H) | Async Resolv exit window |
ustb | Up to 48h (P2D) | Superstate redemption window |
POST /v2/portfolio-wallets/withdrawal-time-estimates — see Estimate withdrawal times.
Estimate withdrawal times
Estimate end-to-end withdrawal times for a set of positions and destination:| Field | Required | Description |
|---|---|---|
destinationToken | Yes | Destination token (only usdc today) |
destinationChain | Yes | ethereum, arbitrum, base, polygon, or optimism |
positions | Yes | Positions array (same shape as wallet creation) |
unwind_liquidity, convert_to_usdc, bridge_cctp, payout_transfer_and_finality) with p50, p90, and max durations. fullSettlementTime aggregates across steps.
"p90": "PT1H30M" means “90th percentile is about 1 hour 30 minutes”. These are best-effort estimates, not an SLA.
Withdrawal webhooks
Subscribe to these events for real-time tracking:portfolio_wallet.withdrawal.status_changedportfolio_wallet.withdrawal.payout.status_changed
Bridge domains reference
A bridge domain is a hard boundary for where liquidity can come from. The withdrawal engine never crosses bridge domain boundaries.USDC unified (CCTP)
USDC on CCTP-supported chains forms a singleusdc:unified domain: Ethereum, Arbitrum, Base, Polygon, Optimism, Solana.
A withdrawal to any of these chains can source USDC from any other chain in the domain (via CCTP bridging).
BSC (always isolated)
BSC USDC is isolated:usdc:bsc. Cannot be bridged to or from CCTP chains.
How cash fits in
Within a bridge domain, cash is always the first source of liquidity. Only if cash is insufficient will the system unwind yield positions. ThesourceLegs[] array shows the sourcing order.
Next steps
- Transaction Approvals — approve withdrawals via Turnkey
- Webhooks — subscribe to withdrawal and payout events
- Balances and Yield — monitor balance changes after withdrawal