Your First Withdrawal
Relevant if you withdraw Exchange balances to your own bank account or wallet. If CrissCross settles to you on a fixed schedule and you never withdraw through the API, skip this page. Sending money to third parties is a payout, not a withdrawal — test that with Your First Payout.
This test registers a beneficiary, waits for it to become active, withdraws to it, and follows the withdrawal to completed. It proves the parts of Exchange that trading doesn’t: idempotent writes, the beneficiary lifecycle, and how a withdrawal reserves and then debits your balance.
The example uses a USDT wallet on Ethereum. If you withdraw in fiat, the steps are the same; see Fiat beneficiaries below for the routing details.
This page is a test procedure. For how beneficiaries and withdrawals behave, see Funding.
Before you start
You need a USDT balance of at least 100.00. Either ask your account manager to top up your sandbox organisation in USDT, or trade into it by following Your First Trade with "buyCurrency": "USDT".
You’ll also need an access token from your sandbox credentials — see Step 1 of Your First Trade.
Step 1 — Create a beneficiary
POST /v1/beneficiaries — API reference
Pass: a 201 with a beneficiaryId and a status of pending — or active, if its checks have already passed. Keep the beneficiaryId.
A few things worth getting right the first time:
- Send an
Idempotency-Key. It’s optional, but without one a retried request can create a duplicate beneficiary. Generate a UUIDv4, store it before you send, and reuse it on every retry of the same request. - Crypto beneficiaries need
isAddressOwnerandsendTo.trueandprivate_walletmean it’s your own self-custodied wallet. Paying someone else, or an account at another exchange, needs more fields — see Create beneficiary. - One currency per beneficiary. A wallet you want to receive both USDT and USDC at is two beneficiaries.
If this fails: a 400 means a field is missing or invalid, often in routingDetails. The error.message names the field.
Step 2 — Wait for the beneficiary to activate
GET /v1/beneficiaries/{beneficiaryId} — API reference
Pass: status is active and activatedAt is set. Poll every few seconds while it’s pending. If it stays pending, ask your account manager to approve it — see Exchange Sandbox for which beneficiaries need approval in the sandbox.
In production this step runs real compliance checks and can take longer, or end in rejected. Build your code to wait for active — by polling or on the beneficiary.active webhook — rather than assuming it.
Step 3 — Create the withdrawal
POST /v1/withdrawals — API reference
Pass: a 201 with a withdrawalId and a status of pending. Check Get balances: USDT available has dropped by 100.00 and reserved has gone up by the same amount.
The amount is reserved as soon as the withdrawal is created, so it can’t be spent twice. It only becomes a final debit when the withdrawal completes.
If this fails: BENEFICIARY_NOT_ACTIVE means you didn’t wait for Step 2. CURRENCY_MISMATCH means currency doesn’t match the beneficiary’s. INSUFFICIENT_BALANCE means your available USDT doesn’t cover amount — remember that other pending withdrawals hold part of your balance in reserved.
Step 4 — Poll to a final status
GET /v1/withdrawals/{withdrawalId} — API reference
The sandbox dispatches withdrawals in a batch every 5 minutes, so expect pending for up to 5 minutes, then processing, then completed. Poll every 30 seconds or so.
Pass: status reaches completed. Your USDT reserved has gone back down by 100.00 and total is 100.00 lower than before, and the USDT balance statement shows a withdrawal entry with your withdrawalId as its relatedId.
Stop polling at completed, cancelled, failed or returned.
What you have just proved
Then try the failures
As with trading, there are no trigger values. You produce each outcome by setting up the state that causes it:
The sandbox can’t produce a failed or returned withdrawal, a rejected beneficiary, or a rejected deposit on demand. Test your handlers for those against the example payloads in Exchange Webhooks, and make sure a returned withdrawal restores the balance in your records.
Fiat beneficiaries
In production, a fiat beneficiary is only approved once its compliance documents have been reviewed. The sandbox doesn’t ask for documents.
Fiat beneficiaries need routing details for their rail. USD takes international_wire or domestic_wire with a wireDetails object, and swift is rejected for new beneficiaries. See Funding.
Next
- Subscribe your Exchange webhook endpoint to the
beneficiary.*andwithdrawal.*events, and re-run this test. - When the Exchange tests pass, work through Going Live.