Goal
Submit an on-chain crypto transfer from a funded account and track it to confirmation. The destination can be an external wallet, exchange deposit address, or another Bastion account’s deposit address. For instant off-chain transfers between omnibus accounts, see Send crypto off-chain.Key entities and concepts
Gas fees: Bastion sponsors gas by default on all supported chains. Your customers do not need to hold native tokens (SOL, ETH, etc.). Gas sponsorship is opt-out. Contact your Bastion account team to disable.
Supported chains: Segregated accounts support Solana and EVM chains. Omnibus accounts support Solana and EVM chains for on-chain withdrawals. See supported chains and assets and the supported assets endpoint.
Flow overview
- Confirm the customer’s identity is verified and they have sufficient balance.
- Submit
POST /v2/crypto/transferswithaddress_destination. - Track transfer status via webhooks or polling until it reaches a terminal state.
- Show the completed transfer and updated balances in your app.
Prerequisites
- The customer has a verified identity (KYC or KYB) in Bastion.
- The customer has an account with balance in a supported asset (e.g., USDC on Solana).
- You have an API key and have registered your webhook.
- Verify the asset and network are supported. See supported chains and assets.
Step 1 — confirm identity, account, and balance
Before sending, confirm the customer can transact and has sufficient balance. Get the identitystatusisACTIVEallowed_to_transact_fungiblesistrue
decimals field to convert to a human-readable amount (e.g., “975000000” with decimals: 6 = 975.00 USDC). Omnibus balances are chain-agnostic. Confirm enough available balance in the available field before submitting. Your UI should surface a clear error if the customer does not have enough balance.
Step 2 — submit on-chain transfer
CallPOST /v2/crypto/transfers to submit an on-chain transfer from the customer’s account.
Request
Response
- Use a unique
request_idas an idempotency key.request_idmust be a UUID. If you retry the same transfer after a transient error, reuse the samerequest_id.
Legacy request body (still accepted)
The flatdestination_address and destination_chain fields are still accepted:
address_destination field.
Step 3 — track transfer status
Track the transfer until it reaches a terminal state. You can use webhooks (recommended) and polling. For full lifecycle definitions, timeout behavior, and failure modes, see Crypto transfers.Webhook — transfer status
Configure a webhook endpoint in the Bastion dashboard to receive transfer events. Bastion delivers asubmit_cryptocurrency_transfer_v2 webhook on every status change, not just terminal states. Your handler should be idempotent and use request_id to correlate events.
Example event (confirmed):
deposit_v2 webhook when the transfer is credited. See Receive crypto deposits.
Handle statuses:
Webhook reliability: Failed deliveries are retried with exponential backoff (15 s base, 10 min max). Deduplicate onid+status; the transferidis stable across status changes, soidalone would drop later updates.
Polling — get a transfer
As a fallback, you can poll transfer status.status and any failure_details to decide what to show to the customer.
Step 4 — show the updated balance
After the transfer confirms, refresh balances and show the transfer in history. Refresh balancesCONFIRMED status. Keep pending states visible for transparency.