Goal
Submit an off-chain transfer between two omnibus accounts on the same platform and confirm it settles. For transfers that go through the blockchain, see Send crypto on-chain.Key entities and concepts
Off-chain transfers require omnibus accounts. Both the sender and recipient must have omnibus accounts on the same platform. If your platform uses segregated wallets, see Send crypto on-chain instead.
Flow overview
- Confirm the customer’s identity is verified and they have sufficient balance.
- Submit
POST /v2/crypto/transferswithaccount_destination. - Track transfer status via webhooks or polling. Off-chain transfers settle immediately.
- 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 omnibus account with balance in a supported asset (e.g., USDC).
- The recipient also has an omnibus account on the same platform.
- You have an API key and have registered your webhook.
Step 1 — confirm identity, account, and balance
Before sending, confirm the customer can transact and has sufficient balance. Get the identitystatusisACTIVEallowed_to_transact_fungiblesistrue
type field is OMNIBUS and chainis CHAIN_UNSPECIFIED. contract_address is omitted.
Confirm enough available balance in the available field for the asset you intend to send. Your UI should surface clear errors when a transfer cannot complete (e.g., insufficient balance).
Step 2 — submit off-chain transfer
CallPOST /v2/crypto/transfers to submit an off-chain transfer from the customer’s account to another account on the same platform.
Request
chain field is needed. The transfer is a ledger operation, not a blockchain transaction.
Request fields:
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. - Both sender and recipient must be omnibus accounts on the same platform. If the destination account is not found, not an omnibus account, or belongs to a different platform, the request returns HTTP 400.
Step 3 — track transfer status
Track the transfer until it reaches a terminal state. You can use webhooks (recommended) and polling. Off-chain transfers settle immediately, so you will typically receive a terminal status within seconds.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. Your handler should be idempotent and use request_id to correlate events.
Sender webhook (confirmed):
destination_address, destination_chain, and transaction_hash are empty because no blockchain transaction occurs. destination_type is ACCOUNT. The events array is always present but empty.
Recipient webhook (deposit credited):
source_type is ACCOUNT and source_account_id identifies the sender. See Receive crypto deposits for full details.
Handle statuses:
Webhook reliability: Failed deliveries are retried with exponential backoff (15 s base, 10 min max). Deduplicate onid+statusfor transfer events (the transferidis stable across status changes), and ondata.dedup_keyfor deposit events.
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 balances- Display amount, asset (USDC), recipient name or account identifier, and status.
- Indicate the transfer was instant (no blockchain confirmation wait).
- For the recipient side: show the sender’s name or account identifier using
source_account_idfrom the deposit webhook.