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.
Prerequisites
Before sending USDC from a wallet, you need:- A customer with a
cst_ID. - A funded OMS wallet with a
wlt_ID containing enough USDC to cover the source amount plus gas (if not sponsoring gas). - A destination: another OMS wallet (its
wlt_ID) or an external onchain address (a0x...address on the same network). - Webhook subscriptions configured in the OMS Dashboard for
transaction.cryptoToCrypto.*events.
Step 1: Create a quote
Create a quote to lock in the pricing. OMS infers the transaction type ascryptoToCrypto from the source and destination asset pair (USDC to USDC).
The quote source must be an OMS crypto wallet: a walletId, asset, and network. The destination describes where the funds land. To send to an external onchain address, set destination.wallet.blockchainAddress. To send to another OMS wallet, set destination.wallet.id instead.
Request
customerId: The customer making this transaction.source.walletId: The OMS custodial wallet holding the USDC to send (string,wlt_prefix).source.asset: The crypto asset to send (usdcorusdt).source.network: The blockchain the wallet is on (polygonorethereum).source.amountordestination.amount: Exactly one is required. Here we specify the source amount (send 2,000 USDC). OMS calculates the destination amount after fees. You could alternatively specifydestination.amountto target an exact received amount.destination.wallet: Where the funds land. Provide one ofid(another OMS wallet),blockchainAddress(an external onchain address), orexternalAccount(a saved bank account, early access). This example usesblockchainAddress.destination.asset:usdc.destination.network: The network the funds are delivered on. For a crypto-to-crypto send this matches the source network (polygon).
sponsorGas: Whentrue, the developer absorbs gas fees. The customer sees fees on the source side as zero and the actual gas cost appears insponsorGasCost.developerFees: Array of fee entries. Omitted here, so no developer fee is charged.metadata: Up to 20 arbitrary key-value pairs.
Response, 201 Created
id: The quote ID (prefixqt_). You’ll use this to create the transaction.status: "open": Pricing is locked, awaiting acceptance.type: "cryptoToCrypto": Inferred from a USDC source and USDC destination.destination.amountNet: "1998.50": What the destination address receives. Calculated as: 2,000.00 USDC − 0.00 developer fee = 1,998.50 USDC.sponsorGasCost: "0.38": The estimated gas cost the developer will absorb. This is an out-of-band cost, not deducted from the destination amount.destination.feesDeducted.gas: "0.00": Shows as zero to the customer because gas is sponsored.expiresAt: Trails-routed quotes have a ~5-minute TTL.
Step 2: Create the transaction
Accept the quote by creating a transaction that references thequoteId. This is the point of no return: USDC is pulled from the wallet and the onchain send begins.
Request
txn_.
Response, 201 Created
status: "processing": USDC has been pulled from the wallet and the onchain send is underway.subStatus: "processing.fundsPulled": Granular sub-status using dot notation, indicating the USDC has been pulled. The next sub-status reflects send progress.source.txHash: "0x7f2a9b...c3d4e5": The onchain transaction hash for the send.sponsorGasCost: "0.42": Final gas cost (may differ slightly from the quote estimate of $0.38).
Webhook, transaction.cryptoToCrypto.processing
OMS fires this webhook when the transaction enters processing:
Step 3: Track the transaction
OMS pulls the USDC, broadcasts the onchain send, and the funds arrive at the destination address once the transaction confirms.Webhook, transaction.cryptoToCrypto.completed
processing webhook:
statusis nowcompletedandsubStatusisnull.updatedAtreflects when the onchain send confirmed.
Polling alternative
If you prefer polling over webhooks, retrieve the transaction directly:status is completed or failed. Webhooks are preferred for production: they avoid unnecessary requests and notify you the moment status changes.
Failure handling
If the transaction fails after USDC has been pulled (for example, an onchain error or a compliance block), OMS fires atransaction.cryptoToCrypto.failed webhook:
transaction.cryptoToCrypto.refundCompleted webhook.
Compliance review
In some cases, a transaction may be held for compliance review. This fires a developer-only webhook (not forwarded to customers):processing during review. Once resolved, it proceeds to completed or failed.
Webhook events
| Event | When |
|---|---|
transaction.cryptoToCrypto.processing | USDC pulled, onchain send underway |
transaction.cryptoToCrypto.completed | Funds delivered to the destination |
transaction.cryptoToCrypto.failed | Send failed (onchain error, compliance, etc.) |
transaction.cryptoToCrypto.refundCompleted | USDC refund returned to source wallet |
transaction.cryptoToCrypto.underReview | Under compliance review (developer-only) |
Key points
- The quote is the contract. The transaction request body is just
{ "quoteId": "..." }: no overrides. - The source must be an OMS wallet. A quote
sourceis always{ "walletId", "asset", "network" }. Send to another OMS wallet withdestination.wallet.idor to an external address withdestination.wallet.blockchainAddress. - Set
sponsorGasto absorb gas for the customer. Pulling and sending USDC has gas costs on the source side. WithsponsorGas: true, the customer sees zero source-side fees and the developer absorbs the cost reported insponsorGasCost. source.txHashis populated because OMS executes an onchain transaction to send the USDC.sponsorGasCostmay differ between quote and transaction. The quote gives an estimate; the transaction shows the actual cost. Plan for minor variance.- No developer fee in this example. Omitting
developerFeesmeans the developer takes no cut. Add fee entries to the quote request to charge. - Idempotency keys are per request. Use a distinct
Idempotency-Keyfor the quote and the transaction so retries are safe and don’t create duplicates.
Pay out to a bank account
Bank payout destinations are 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 bank payout destinations are available.
source is still an OMS wallet holding USDC. The difference is the destination: instead of a crypto address, you target a saved external bank account using destination.wallet.externalAccount (a cpUsBank_ ID) with asset: "usd" and a fiat rail in network (ach or wire). OMS infers the transaction type as cryptoToFiat.
External bank accounts have no endpoints yet, so this path is early access.
{ "quoteId": "..." }. Track it with transaction.cryptoToFiat.* webhooks or by polling GET /v0.9/transactions/{id}. ACH transfers typically settle in 1 to 3 business days; wire is faster. The same operational notes apply: the quote is the contract, sponsorGas covers the source-side gas reported in sponsorGasCost, and you should use a distinct idempotency key per request.