Before you start: the OMS API is in early access. Every endpoint, including the ones in this guide, requires an early-access API key. Request access before you begin.
Virtual account provisioning and management is not yet available in the OMS API. To be notified when it launches, register your interest.
Register interest
Share your use case and we’ll reach out when virtual account provisioning and management is available.
GET /transactions/{id} and GET /customers/{id}/transactions) are available now.
Prerequisites
Before creating a Virtual Account, you need:- A customer with a
cst_ID who has theusdendorsement active. - A destination wallet: a custodial wallet (
wlt_) to receive the converted crypto. - Webhook subscriptions configured in the OMS Dashboard for the relevant
transaction.fiatToCrypto.*events.
Step 1: Create the Virtual Account
Creating a virtual account is not yet available in the OMS API. The request and response below describe the intended shape so you can plan your integration.
fiatToCrypto transaction. No amount is specified upfront: the amount is determined when the deposit lands.
Request
source.asset:usd(the only supported fiat).source.network:usBank(the only supported rail). The specific routing details are assigned by OMS and surfaced in the response.destination.wallet: AWalletTargetobject with one ofwallet,blockchainAddress, orexternalAccount(cpBcAddr_prefix).destination.asset: Target crypto asset (usdcorusdt).destination.network: Target rail (polygonorethereum).
developerFees: Percentage-only because the deposit amount is unknown until arrival. Each entry takes an optionalwalletdirecting that fee to a specific OMS wallet.accountholderName: Whose name appears on the bank account:customer,oms(default), ordeveloper.label,bankMemo,metadata(up to 20 keys).
Response: 201 Created
id: The Virtual Account ID (prefixva_). Use this for GET, PATCH, and DELETE operations.status: Starts asactive. Can be changed topausedordeletedlater.source: Echoes the requested asset (usd) and rail (usBank).bankDetails: Top-level field with the assigned bank account number, routing number, owner name, and a uniquereferencestring. Server-generated and unique to this Virtual Account. Surface these to your customer as the deposit destination.accountholderName: Whose name appears on the bank account (customer,oms, ordeveloper). Defaults tooms.omsFeeSchedule: Shows how OMS fees will be calculated on auto-created transactions. Since there is no Quote step for Virtual Accounts, this gives you visibility into the fee structure upfront.
bankDetails to your customer as their dedicated bank account details. Store va_01H9Xv... in your system to link incoming transactions back to this account.
Test in sandbox
Once a virtual account is provisioned for you, you can simulate an inbound fiat transfer in sandbox instead of sending real funds. CallPOST /virtual-accounts/{virtualAccountId}/simulate with a rail (ach_in, wire_in, or swift_in) and an amount in cents. OMS fires the same transaction.fiatToCrypto.* webhooks as a real deposit. This endpoint returns 404 in production.
Step 2: Customer Sends Fiat
The customer initiates an ACH or wire transfer from their bank to the account and routing number inbankDetails. There is nothing to do on the API side at this point: OMS monitors the assigned bank account for incoming fiat.
For this example, the customer sends 500.00 USD via ACH to account 8675309123.
Step 3: OMS Detects the Deposit and Auto-Creates a Transaction
When OMS receives the fiat deposit, it automatically creates a Transaction inprocessing status. This transaction skips the Quote step entirely: there is no Quote object, and quote is null.
Webhook: transaction.fiatToCrypto.processing
OMS fires this webhook as soon as the transaction is created:
quote: null: Auto-created transactions skip the Quote step.virtualAccount: "va_01H9Xv...": Links this transaction back to the originating Virtual Account.source.amountGross: "500.00": The actual fiat amount deposited, now known.source.amountNet: "494.00": After deducting the 5.00 developer fee and 1.00 OMS fee (from theomsFeeSchedule).source.feesDeducted.developer: "5.00": Your 1.0% fee on the 500.00 USD deposit.- The
metadatafrom the Virtual Account is carried over to the transaction.
Polling Alternative
If you prefer polling over webhooks, you can retrieve the transaction directly:Step 4: Transaction Completes
OMS converts the fiat to USDC and delivers it to the destination wallet on Polygon.Webhook: transaction.fiatToCrypto.completed
processing webhook:
statusis nowcompletedandsubStatusisnull(sub-statuses only apply to the cash pickup flow).destination.txHash: The onchain transaction hash confirming USDC delivery to the wallet.updatedAtreflects the completion time.
wlt_01H9Xf..., and 494.00 USDC has been delivered to wlt_01H9Xb....
Step 5 (Reuse): More Deposits
The Virtual Account remainsactive and continues monitoring accountNumber: 8675309123. Every subsequent ACH transfer to that account number triggers the same flow: a new auto-created transaction with a new txn_ ID, the same virtualAccount, and fees calculated from the same configuration.
There is no limit on the number of transactions a single Virtual Account can produce.
Managing Virtual Accounts
The management operations below (pause, resume, change destination, delete, and list) are not yet available in the OMS API. They describe the intended pattern.
Pause a Virtual Account
Pausing stops OMS from processing new deposits. Any in-flight transactions continue to completion."status": "paused".
Resume a Virtual Account
Change the Destination
You can redirect future deposits to a different wallet without creating a new Virtual Account. The source (and its bank account number) remain the same.Delete a Virtual Account
Soft-deletes the account. OMS stops processing new deposits.204 No Content.
List Virtual Accounts
Failure Handling
If a transaction fails after the deposit is detected, OMS fires atransaction.fiatToCrypto.failed webhook with an error object:
transaction.fiatToCrypto.refundCompleted webhook. If the refund itself fails, you’ll receive transaction.fiatToCrypto.refundFailed.
Compliance Review
In some cases a transaction may be held for compliance review. This fires a developer-only webhook (not forwarded to customers):processing status during review. Once resolved, it proceeds to completed or failed with the corresponding webhook.
Summary: Webhook Events for Virtual Account Transactions
| Event | When |
|---|---|
transaction.fiatToCrypto.processing | Fiat deposit detected, transaction created |
transaction.fiatToCrypto.completed | Crypto delivered to destination wallet |
transaction.fiatToCrypto.failed | Delivery failed |
transaction.fiatToCrypto.refundCompleted | Fiat refund sent back to sender |
transaction.fiatToCrypto.refundFailed | Refund attempt failed |
transaction.fiatToCrypto.underReview | Under compliance review (developer-only) |
Key Differences from Deposit Addresses
Virtual Accounts and Deposit Addresses are both persistent, auto-triggering configurations, but they serve different source types:| Deposit Addresses | Virtual Accounts | |
|---|---|---|
| Source | Crypto (on-chain) | Fiat (usBank rail) |
| Deposit details | On-chain address (in source.depositInstructions) | Dedicated bank account (top-level bankDetails) |
| Transaction type | cryptoToCrypto | fiatToCrypto |
| ID prefix | da_ | va_ |
| Developer fees | Percentage only | Percentage only |
| Quote step | None: auto-created | None: auto-created |