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.Authenticate by exchanging your API key and secret for a bearer token at
POST /auth/token, then send it as Authorization: Bearer {accessToken} on every request. Every mutating request (POST and PATCH) also requires an Idempotency-Key header. See Get started for the full flow.Prerequisites
Before creating a cash-in, you need:- A customer with a
cst_ID and theusdendorsement active (required for cash flows). - A cash location, retrieved from
GET /cash-locationswithprovider,latitude,longitude, andflow=cash_in. This is the retail location where the customer will deposit cash. Each result includes alocIdand acashLocationReference, which you pass on the cash-in’scashfield aslocationIdandlocationReferencerespectively. - A crypto destination wallet: an OMS custodial wallet (
wlt_) referenced bydestination.wallet.id, or an external wallet referenced bydestination.wallet.blockchainAddressordestination.wallet.externalAccount. - A webhook endpoint subscribed to the
cashIn.*andtransaction.fiatToCrypto.*events, registered withPOST /webhooksor in the OMS Dashboard.
Limits
The following limits apply to cash-ins:| Limit | Value |
|---|---|
| Transaction minimum | $20 |
| Transaction maximum | $500 ($1,000 at Walmart locations) |
| Daily maximum | $1,500 and 3 transactions |
| Weekly maximum | $3,500 and 12 transactions |
| Monthly maximum | $5,000 and 20 transactions |
How Cash-In Works
Cash-In is a specialized, ephemeral flow for in-person cash deposits. Unlike Deposit Addresses (persistent, reusable, crypto-source), a cash-in generates a one-time deposit code valid for 1 hour. The customer presents the code at a retail location, deposits cash, and OMS auto-converts to crypto. Key differences from other funding methods:- Indicated amount, not exact amount. You provide an optional
source.indicatedAmount. OMS uses this to generate upfront estimates. The actual deposit amount is determined by how much cash the customer deposits at the counter. If you omitindicatedAmount, no estimates are returned. - Amounts are estimated at creation, finalized after deposit. At creation the
sourceanddestinationamounts are derived fromindicatedAmount. Once the customer deposits cash, they are recalculated on the actual amount. - No quote step. Cash-In auto-creates a transaction when the deposit is received, similar to Deposit Addresses and Virtual Accounts.
- Gas sponsorship.
sponsorGasdefaults totrue, so OMS absorbs the on-chain gas for the destination delivery. The absorbed cost appears as the top-levelsponsorGasCost. At launch all fees and gas are0.00.
Step 1: Create a Cash-In Code
customerId, source, destination, and cash.
source.asset (required): the deposit currency; usd.
source.indicatedAmount (optional): the expected deposit amount. OMS uses this value to generate upfront estimates in the response. If omitted, no estimates are returned. The actual deposit amount is determined by how much cash the customer deposits at the location.
destination (required): the crypto the deposit is converted to. Set asset and network, and identify the wallet with wallet.id (an OMS wallet, wlt_), wallet.blockchainAddress (an external on-chain address), or wallet.externalAccount (a registered external account).
cash (required): the retail location. Set locationId (from the locId field of GET /cash-locations) and locationReference (from the cashLocationReference field), which selects a specific store.
sponsorGas (optional): defaults to true, so OMS absorbs the on-chain gas for the destination delivery. Only true is currently supported.
Response, 201 Created
depositInstructions.code: the code they present at the retail counter (“483 291”).depositInstructions.locationNameandlocationAddress: where to go.depositInstructions.expiresAt: the code is valid for 1 hour.- The estimated economics based on
indicatedAmount($200.00):sourceanddestinationeach carryamountGross,amountNet, and afeesDeductedbreakdown, and the top-levelratesobject carriespair,exchangeRate, andeffectiveRate. Show the customer what they would receive at this deposit amount.
0.00. transactionId is null until the deposit is confirmed. OMS fires the cashIn.created event when the code is issued.
Step 2: Customer Deposits Cash
The customer visits the location and presents their code at the counter. This step happens outside OMS, at the retail location’s payment terminal. OMS is notified when the deposit is received. If the code expires before the customer arrives, you can refresh it (see “Refreshing an Expired Code” below).Step 3: Deposit Received, Cash-In Completes
Once the retail partner confirms the deposit, OMS recalculates pricing on the actual amount, converts USD to the destination crypto, and delivers it. The cash-in moves tocompleted, OMS fires the cashIn.completed event, and the cash-in’s transactionId is populated. Read the cash-in to see the final state:
statusis nowcompletedandsubStatusissettled.- The
sourceanddestinationamounts are recalculated on the actual deposit amount (they were estimated fromindicatedAmountat creation). completedAtis populated.transactionIdlinks to the auto-created transaction (thetxn_ID). Read that transaction to see the on-chain delivery hash.
Step 4: Auto-Created Transaction
When the cash-in completes, OMS auto-creates a transaction withsourceToDestination: cashToCrypto. It follows the standard processing → completed lifecycle, firing the fiat-funded transaction events (transaction.fiatToCrypto.*). Its precursor identifies the originating cash-in.
Webhook: transaction.fiatToCrypto.processing
precursor.type: "cashIn": links this transaction back to the originating cash-in (details.cashInId).sourceToDestination: "cashToCrypto": the source was a cash deposit; the destination is crypto.source.type: "cash": there is no blockchain transaction on the source side.- All amounts live in
pricing; the sides carry only identity and instrument detail.
Webhook: transaction.fiatToCrypto.completed
OMS fires transaction.fiatToCrypto.completed when the crypto lands. The envelope is the same shape; in the payload, status is now completed, subStatus is null, and destination.details.txHash is populated with the on-chain hash.
At this point the flow is done. The customer deposited $200.00 cash at CVS Pharmacy #4521, and the converted USDC was delivered to wallet wlt_01H9Xb... on Polygon. At launch all fee components are 0.00.
Refreshing an Expired Code
If the customer doesn’t arrive before the code expires, refresh it to issue a new code with a new 1-hour window. The previous code is invalidated.depositInstructions.code and an expiresAt extended by another hour. Everything else stays the same:
pending status. If the cash-in has already completed or expired, the call returns 422 Unprocessable.
Code Expiration
If the code expires without a deposit, the cash-in moves toexpired and OMS fires the cashIn.expired event. You can also detect this by reading the cash-in:
"status": "expired" and transactionId: null. An expired cash-in is terminal: no transaction is created and no money moves. If the customer still wants to deposit cash, create a new cash-in.
Polling
You can read the cash-in status at any time:{ data: [...], hasMore, nextCursor }. Page with the limit and cursor query parameters.
Failure Handling
Cash-In failures can happen at two levels: Cash-In level: The code expires before the customer deposits cash. The cash-in moves toexpired and no transaction is created. No money moved, no refund needed. OMS fires cashIn.expired; you can also detect this by reading the cash-in.
Transaction level: The cash deposit is received but crypto delivery fails. The auto-created transaction moves to failed and you receive a transaction.fiatToCrypto.failed event with the payload’s error object populated:
Compliance Review
A transaction triggered by a cash-in may be held for compliance review. It stays inprocessing with subStatus processing.underReview. Do not surface this to the customer. Once resolved, the transaction proceeds to completed or failed, firing transaction.fiatToCrypto.completed or transaction.fiatToCrypto.failed.
Summary: Webhook Events
The cash-in flow delivers two event streams: the cash-in’s own lifecycle events, and the fiat-funded transaction events fired on the auto-created transaction. See Webhook events for the envelope and the full catalog.| Event | When |
|---|---|
cashIn.created | The deposit code was issued |
cashIn.completed | The cash was deposited and converted; transactionId links the auto-created transaction |
cashIn.expired | The code expired before a deposit was made |
transaction.fiatToCrypto.processing | Cash received, crypto delivery underway |
transaction.fiatToCrypto.completed | Crypto delivered to the destination |
transaction.fiatToCrypto.failed | Crypto delivery failed; see payload.error |
payload. Branch on the event name or payload.status; treat subStatus as optional operational detail.
Key Points
- Indicated amount for upfront estimates. Pass an optional
source.indicatedAmount. OMS returns estimated amounts at creation; final amounts are calculated when the customer deposits. If omitted, no estimates are returned. - Amounts and rates on the cash-in. The
sourceanddestinationeach carryamountGross,amountNet, and afeesDeductedbreakdown; the top-levelratesobject carriespair,exchangeRate, andeffectiveRate. At launch all fee components are0.00. - Gas sponsorship.
sponsorGasdefaults totrue; OMS absorbs the destination gas. The absorbed cost shows as the top-levelsponsorGasCost("0"at launch). - Codes expire in 1 hour. Call
POST /cash-ins/{cashInId}/refreshto issue a new code if the customer needs more time. Only works whilestatusispending. - Cash-In is single-use. Once a deposit is received or the code expires, the cash-in is terminal. Create a new one for the next deposit.
- Two event streams. The cash-in fires
cashIn.created,cashIn.completed, andcashIn.expired; the auto-created transaction firestransaction.fiatToCrypto.*. Subscribe to both, or read the cash-in withGET /cash-ins/{cashInId}if you prefer polling. - The source is cash. There is no blockchain transaction on the source side; the auto-created transaction’s
sourceToDestinationiscashToCrypto. - The destination is an OMS or external wallet. Identify it with
destination.wallet.id(an OMS wallet,wlt_),destination.wallet.blockchainAddress(an external on-chain address), ordestination.wallet.externalAccount(a registered external account).