Withdrawal Workflow
End-to-end guide to crypto withdrawals: initiate on a network, receive the verification code by webhook, confirm, and track the withdrawal until it settles on-chain.
Sending crypto out is the most sensitive thing an account does, so every withdrawal takes two steps: you request it, then you confirm it with a verification code. Nothing leaves the platform until the code checks out, and the withdrawal only counts as done when it settles on-chain.
Authentication: every endpoint in this guide accepts your Owner API Key (x-api-key) or the user's token (Authorization: Bearer). Each endpoint is documented in full on Withdrawals.
Overview
- Initiate the withdrawal with currency, amount, address and network. It starts
PENDING. - Receive the verification code in the
user.withdrawal.verification_code_issuedwebhook event. - Confirm with the code. The withdrawal moves to
PROCESSING. - Read the withdrawal until it's
COMPLETEDorFAILED.
Prerequisites:
- An Owner API Key (
x-api-key) or the user's token (Authorization: Bearer). - An active user with enough available balance in the currency.
- A webhook endpoint configured in Customer webhooks to receive the code.
Step-by-Step Guide
Step 1: Initiate the withdrawal
Send the currency, a decimal-string amount, the destination address and the network. network is required because one asset can live on several networks. Add an x-idempotency-key so a retry after a timeout returns the same withdrawal instead of creating another.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/5299e81c-3a8c-5b64-a112-b93bf43de74b/accounts/crypto/withdrawals" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-idempotency-key: 9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c" \
-d '{
"currency": "BTC",
"amount": "0.01000000",
"address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"network": "BITCOIN",
"memo": "Withdrawal to hardware wallet"
}'The response is the withdrawal in PENDING with a verification block. Keep the id: every later call uses it.
{
"success": true,
"data": {
"id": "3b1f0c52-7d4e-4a9b-8c21-5e6f7a8b9c0d",
"object": "withdrawal",
"atx_id": "3b1f0c52-7d4e-4a9b-8c21-5e6f7a8b9c0d",
"status": "PENDING",
"status_reason": null,
"user_id": "5299e81c-3a8c-5b64-a112-b93bf43de74b",
"customer_id": "corp-redundant-firewall",
"wallet_id": null,
"debited_wallets": [],
"currency": "BTC",
"amount": "0.01000000",
"fee": null,
"address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"network": "BITCOIN",
"memo": "Withdrawal to hardware wallet",
"tx_hash": null,
"idempotency_key": "9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c",
"created_at": "2026-10-12T14:03:11Z",
"updated_at": "2026-10-12T14:03:11Z",
"completed_at": null,
"created_by": "12015942-4d29-5733-a774-14ee16e31da3",
"updated_by": "12015942-4d29-5733-a774-14ee16e31da3",
"verification": {
"expires_at": "2026-10-12T14:08:11Z",
"attempts_remaining": 3
}
},
"meta": {
"timestamp": "2026-10-12T14:03:11Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Step 2: Receive the verification code
Sovera never returns the code in an API response. Your webhook endpoint receives it in the user.withdrawal.verification_code_issued event, with withdrawal_id, user_id, code, expires_at, trigger, attempts_remaining, amount, currency, network and destination_address. The code has 6 digits, lasts 5 minutes and allows 3 attempts. When the treasury has to be replenished first, the code reaches Sovera by email: it then lasts 15 minutes from the email, the event always says trigger: "issued" and has no attempts_remaining, and custody counts the attempts. See the full event in Webhooks.
Show the code to your user to approve the withdrawal, or confirm it from your backend if your own flow already authorized it.
Step 3: Confirm with the code
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/5299e81c-3a8c-5b64-a112-b93bf43de74b/accounts/crypto/withdrawals/3b1f0c52-7d4e-4a9b-8c21-5e6f7a8b9c0d/confirm" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{ "code": "482913" }'A valid code returns the withdrawal in PROCESSING; once the funds are debited, the wallets are listed in debited_wallets (it can still be empty when the treasury is being replenished first). The funds are on their way, but the transaction isn't settled yet. If Sovera sent the request but lost the answer, the withdrawal is PROCESSING with status_reason.code EXECUTION_OUTCOME_UNKNOWN: don't confirm again. Read the withdrawal until it settles: no webhook announces the final status, and a failure is announced by ledger.wallet.credited with reason withdrawal_reversal when the funds come back.
- Wrong code:
400 INVALID_VERIFICATION_CODE, and the message says how many attempts remain. - Third wrong code:
403 VERIFICATION_BLOCKED. The withdrawal isBLOCKED; cancel it and start again. - Code lapsed:
422 INVALID_STATE_TRANSITION(or400 VERIFICATION_CODE_EXPIRED). The withdrawal isEXPIRED; initiate a new one.
Step 3b (optional): Resend the code
Lost the code, or it's about to lapse? Request a new one while the withdrawal is still PENDING. A new event arrives with trigger: "resent" (issued when the code comes by email), and the old code stops working.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/5299e81c-3a8c-5b64-a112-b93bf43de74b/accounts/crypto/withdrawals/3b1f0c52-7d4e-4a9b-8c21-5e6f7a8b9c0d/verification-codes" \
-H "x-api-key: YOUR_API_KEY"Step 4: Track the withdrawal until it settles
Read the withdrawal until status is COMPLETED or FAILED. Records are permanent, so you can read it at any time.
curl -X GET "https://api.sandbox.sovera.io/sovx/v1/users/5299e81c-3a8c-5b64-a112-b93bf43de74b/accounts/crypto/withdrawals/3b1f0c52-7d4e-4a9b-8c21-5e6f7a8b9c0d" \
-H "x-api-key: YOUR_API_KEY"COMPLETED:tx_hash,completed_atand the real networkfee({ amount, currency, paid_by }) are set;feestaysnullif the network didn't report an exact amount.FAILED:status_reasonexplains why, e.g.{ "code": "NETWORK_FAILED", "message": "The network rejected the transaction." }.
To find withdrawals in flight, list them with a status filter:
GET https://api.sandbox.sovera.io/sovx/v1/users/{{userID}}/accounts/crypto/withdrawals?filter[where][status][inq][]=PENDING&filter[where][status][inq][]=PROCESSINGCancel (optional)
Cancel a PENDING or BLOCKED withdrawal before it's confirmed. It turns CANCELLED and nothing is debited. A PROCESSING withdrawal can't be cancelled.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/5299e81c-3a8c-5b64-a112-b93bf43de74b/accounts/crypto/withdrawals/3b1f0c52-7d4e-4a9b-8c21-5e6f7a8b9c0d/cancel" \
-H "x-api-key: YOUR_API_KEY"Troubleshooting
The code never arrives
- Check that your webhook endpoint is configured and verified in Customer webhooks.
- Keep the deposit email poller configured too: when the treasury needs replenishing first, the code reaches Sovera by email before it's forwarded to you.
- Resend the code while the withdrawal is
PENDING.
422 VALIDATION_ERROR on /network
The network is missing or the currency doesn't run on it. The message lists the networks the currency supports, e.g. network must be one of: ETHEREUM for USDT.
400 INSUFFICIENT_BALANCE
The user's available balance in that currency is below the amount. Check the user balance first.
403 FORBIDDEN
The account is frozen, or a user token tried to act on another user.
422 INVALID_STATE_TRANSITION
The withdrawal isn't in a status that allows the action, e.g. confirming a withdrawal that's already PROCESSING. Read it to see its current status.
503 SERVICE_UNAVAILABLE
Crypto withdrawals aren't enabled for your account yet. Contact support.
Best Practices
- Always send
network. Never assume a default network for an asset. - Send an
x-idempotency-keyon initiate and confirm, and reuse it on retries. - Treat
PROCESSINGas in flight, not done. OnlyCOMPLETEDmeans the funds settled. - Never log or expose the code. Pass it only to the user who approves the withdrawal.
- Verify the destination address before you initiate.
Next Steps
- Withdrawals reference: every field, status and error.
- Account data: balances and history.
- API conventions: envelope, errors, pagination and idempotency.
Support
Need help? Email [email protected]
Trading Workflow
Execute a trade end to end: pick a pair, lock a quote, place the order, track it, and check the wallet balance.
Webhook payload fixes (2026-10-15)
Corrections to the October 2026 webhook payloads: one balance rule for every ledger and funds event, canonical network codes, real amounts, on-chain hashes only, the new user.funds.released event and the end of the legacy code body.