Skip to main content
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.
Cash-in flow
1AppOMSPOST /cash-ins (source, destination, cash)
2OMSApp{depositInstructions: {code: “XXX XXX”, expiresAt}}
3AppCustomerShow deposit code + nearby location
4CustomerRetailPresent code, hand over cash
5RetailOMSConfirm deposit + actual amount
6OMSAuto-create cashToCrypto transaction
7OMSAppWebhooks: cashIn.completed, transaction.fiatToCrypto.completed

Prerequisites

Before creating a cash-in, you need:
  1. A customer with a cst_ ID and the usd endorsement active (required for cash flows).
  2. A cash location, retrieved from GET /cash-locations with provider, latitude, longitude, and flow=cash_in. This is the retail location where the customer will deposit cash. Each result includes a locId and a cashLocationReference, which you pass on the cash-in’s cash field as locationId and locationReference respectively.
  3. A crypto destination wallet: an OMS custodial wallet (wlt_) referenced by destination.wallet.id, or an external wallet referenced by destination.wallet.blockchainAddress or destination.wallet.externalAccount.
  4. A webhook endpoint subscribed to the cashIn.* and transaction.fiatToCrypto.* events, registered with POST /webhooks or in the OMS Dashboard.

Limits

The following limits apply to cash-ins:
LimitValue
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 omit indicatedAmount, no estimates are returned.
  • Amounts are estimated at creation, finalized after deposit. At creation the source and destination amounts are derived from indicatedAmount. 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. sponsorGas defaults to true, so OMS absorbs the on-chain gas for the destination delivery. The absorbed cost appears as the top-level sponsorGasCost. At launch all fees and gas are 0.00.

Step 1: Create a Cash-In Code

POST /cash-ins
Authorization: Bearer {accessToken}
Idempotency-Key: ci_order_789
Content-Type: application/json
{
  "customerId": "cst_01H9Xa...",
  "source": {
    "asset": "usd",
    "indicatedAmount": "200.00"
  },
  "destination": {
    "asset": "usdc",
    "network": "polygon",
    "wallet": { "id": "wlt_01H9Xb..." }
  },
  "cash": {
    "locationId": "loc_01H9Xd...",
    "locationReference": "R1JFRU5ET1QtMjQzNDpsYXQ9MzguNDk1MTMzLGxuZz0tMTIxLjUwNTMzNg=="
  },
  "sponsorGas": true,
  "metadata": {
    "orderId": "ord_789"
  }
}
Required fields: 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

{
  "id": "ci_01H9Xy...",
  "object": "cashIn",
  "type": "fiatToCrypto",
  "status": "pending",
  "customerId": "cst_01H9Xa...",
  "source": {
    "asset": "usd",
    "indicatedAmount": "200.00",
    "amountGross": "200.00",
    "amountNet": "200.00",
    "feesDeducted": { "total": "0.00", "developer": "0.00", "oms": "0.00", "gas": "0.00" }
  },
  "destination": {
    "asset": "usdc",
    "network": "polygon",
    "amountGross": "199.00",
    "amountNet": "199.00",
    "feesDeducted": { "total": "0.00", "developer": "0.00", "oms": "0.00", "gas": "0.00" },
    "wallet": {
      "id": "wlt_01H9Xb...",
      "blockchainAddress": "0x7B3a9F2c4D1eA8bF6390cE5d2B7fA104C8e3D9b1"
    }
  },
  "cash": {
    "locationId": "loc_01H9Xd...",
    "locationReference": "R1JFRU5ET1QtMjQzNA=="
  },
  "location": {
    "name": "CVS Pharmacy #4521",
    "address": "123 Main St, New York, NY 10001"
  },
  "rates": {
    "pair": "usd/usdc",
    "exchangeRate": "0.9950",
    "effectiveRate": "0.9950"
  },
  "fixedAmountSide": "source",
  "sponsorGas": true,
  "sponsorGasCost": "0",
  "depositInstructions": {
    "code": "483 291",
    "expiresAt": "2026-03-16T19:30:00Z",
    "locationName": "CVS Pharmacy #4521",
    "locationAddress": "123 Main St, New York, NY 10001"
  },
  "transactionId": null,
  "metadata": {
    "orderId": "ord_789"
  },
  "createdAt": "2026-03-16T18:30:00Z",
  "updatedAt": "2026-03-16T18:30:00Z"
}
What to show the customer:
  • depositInstructions.code: the code they present at the retail counter (“483 291”).
  • depositInstructions.locationName and locationAddress: where to go.
  • depositInstructions.expiresAt: the code is valid for 1 hour.
  • The estimated economics based on indicatedAmount ($200.00): source and destination each carry amountGross, amountNet, and a feesDeducted breakdown, and the top-level rates object carries pair, exchangeRate, and effectiveRate. Show the customer what they would receive at this deposit amount.
At launch all fee components are 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 to completed, OMS fires the cashIn.completed event, and the cash-in’s transactionId is populated. Read the cash-in to see the final state:
GET /cash-ins/ci_01H9Xy...
Authorization: Bearer {accessToken}
{
  "id": "ci_01H9Xy...",
  "object": "cashIn",
  "type": "fiatToCrypto",
  "status": "completed",
  "subStatus": "settled",
  "customerId": "cst_01H9Xa...",
  "source": {
    "asset": "usd",
    "indicatedAmount": "200.00",
    "amountGross": "200.00",
    "amountNet": "200.00",
    "feesDeducted": { "total": "0.00", "developer": "0.00", "oms": "0.00", "gas": "0.00" }
  },
  "destination": {
    "asset": "usdc",
    "network": "polygon",
    "amountGross": "199.00",
    "amountNet": "199.00",
    "feesDeducted": { "total": "0.00", "developer": "0.00", "oms": "0.00", "gas": "0.00" },
    "wallet": {
      "id": "wlt_01H9Xb...",
      "blockchainAddress": "0x7B3a9F2c4D1eA8bF6390cE5d2B7fA104C8e3D9b1"
    }
  },
  "cash": {
    "locationId": "loc_01H9Xd...",
    "locationReference": "R1JFRU5ET1QtMjQzNA=="
  },
  "location": {
    "name": "CVS Pharmacy #4521",
    "address": "123 Main St, New York, NY 10001"
  },
  "rates": {
    "pair": "usd/usdc",
    "exchangeRate": "0.9950",
    "effectiveRate": "0.9950"
  },
  "fixedAmountSide": "source",
  "sponsorGas": true,
  "sponsorGasCost": "0",
  "depositInstructions": {
    "code": "483 291",
    "expiresAt": "2026-03-16T19:30:00Z",
    "locationName": "CVS Pharmacy #4521",
    "locationAddress": "123 Main St, New York, NY 10001"
  },
  "transactionId": "txn_01H9Xz...",
  "metadata": { "orderId": "ord_789" },
  "createdAt": "2026-03-16T18:30:00Z",
  "updatedAt": "2026-03-16T18:47:00Z",
  "completedAt": "2026-03-16T18:47:00Z"
}
What changed from the creation response:
  • status is now completed and subStatus is settled.
  • The source and destination amounts are recalculated on the actual deposit amount (they were estimated from indicatedAmount at creation).
  • completedAt is populated.
  • transactionId links to the auto-created transaction (the txn_ 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 with sourceToDestination: 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

{
  "eventId": "evt_01H9Xw2...",
  "eventName": "transaction.fiatToCrypto.processing",
  "resourceType": "transaction_fiat_to_crypto",
  "resourceId": "txn_01H9Xz...",
  "occurredAt": "2026-03-16T18:47:05Z",
  "payload": {
    "id": "txn_01H9Xz...",
    "object": "transaction",
    "sourceToDestination": "cashToCrypto",
    "status": "processing",
    "subStatus": "processing.fundsPulled",
    "customerId": "cst_01H9Xa...",
    "precursor": {
      "type": "cashIn",
      "details": { "cashInId": "ci_01H9Xy..." }
    },
    "source": {
      "party": { "relationship": "customer", "customerId": "cst_01H9Xa...", "entityType": "individual" },
      "type": "cash",
      "category": "cash",
      "details": {
        "asset": "usd",
        "cashLocationId": "loc_01H9Xd...",
        "cashLocationReference": "R1JFRU5ET1QtMjQzNA=="
      }
    },
    "destination": {
      "party": { "relationship": "customer", "customerId": "cst_01H9Xa...", "entityType": "individual" },
      "type": "walletOms",
      "category": "crypto",
      "details": {
        "id": "wlt_01H9Xb...",
        "asset": "usdc",
        "network": "polygon",
        "blockchainAddress": "0x7B3a9F2c...e3D9b1",
        "custodyType": "custodial",
        "txHash": null
      }
    },
    "pricing": {
      "source": { "asset": "usd", "amountGross": "200.00", "amountNet": "200.00", "feesDeducted": { "total": "0.00", "developer": "0.00", "oms": "0.00", "gas": "0.00" } },
      "destination": { "asset": "usdc", "amountGross": "199.00", "amountNet": "199.00", "feesDeducted": { "total": "0.00", "developer": "0.00", "oms": "0.00", "gas": "0.00" } },
      "pair": "usd/usdc",
      "exchangeRate": "0.9950",
      "effectiveRate": "0.9950",
      "fixedAmountSide": "source",
      "sponsorGas": true,
      "sponsorGasCost": "0.00"
    },
    "estimatedArrival": null,
    "error": null,
    "hold": null,
    "metadata": { "orderId": "ord_789" },
    "createdAt": "2026-03-16T18:47:05Z",
    "updatedAt": "2026-03-16T18:47:05Z",
    "expiresAt": null
  }
}
Key fields on the auto-created transaction:
  • 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.
POST /cash-ins/ci_01H9Xy.../refresh
Authorization: Bearer {accessToken}
Idempotency-Key: ci_refresh_789_2
The response is the full cash-in object with a fresh depositInstructions.code and an expiresAt extended by another hour. Everything else stays the same:
{
  "id": "ci_01H9Xy...",
  "object": "cashIn",
  "status": "pending",
  "depositInstructions": {
    "code": "917 042",
    "expiresAt": "2026-03-16T20:30:00Z",
    "locationName": "CVS Pharmacy #4521",
    "locationAddress": "123 Main St, New York, NY 10001"
  },
  "transactionId": null
}
Constraints: Refresh can only be called while the cash-in is in 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 to expired and OMS fires the cashIn.expired event. You can also detect this by reading the cash-in:
GET /cash-ins/ci_01H9Xy...
Authorization: Bearer {accessToken}
The response shows "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:
GET /cash-ins/ci_01H9Xy...
Authorization: Bearer {accessToken}
Or list cash-ins, optionally filtered by customer:
GET /cash-ins?customerId=cst_01H9Xa...
Authorization: Bearer {accessToken}
The list response is { 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 to expired 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:
{
  "eventId": "evt_01H9Xw5...",
  "eventName": "transaction.fiatToCrypto.failed",
  "resourceType": "transaction_fiat_to_crypto",
  "resourceId": "txn_01H9Xz...",
  "occurredAt": "2026-03-16T18:50:00Z",
  "payload": {
    "id": "txn_01H9Xz...",
    "object": "transaction",
    "sourceToDestination": "cashToCrypto",
    "status": "failed",
    "subStatus": null,
    "customerId": "cst_01H9Xa...",
    "precursor": { "type": "cashIn", "details": { "cashInId": "ci_01H9Xy..." } },
    "error": {
      "code": "onChainRevert",
      "message": "Delivery transaction reverted on-chain"
    },
    "metadata": { "orderId": "ord_789" },
    "createdAt": "2026-03-16T18:47:05Z",
    "updatedAt": "2026-03-16T18:50:00Z"
  }
}
Cash refunds are handled outside OMS at the retail location.

Compliance Review

A transaction triggered by a cash-in may be held for compliance review. It stays in processing 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.
EventWhen
cashIn.createdThe deposit code was issued
cashIn.completedThe cash was deposited and converted; transactionId links the auto-created transaction
cashIn.expiredThe code expired before a deposit was made
transaction.fiatToCrypto.processingCash received, crypto delivery underway
transaction.fiatToCrypto.completedCrypto delivered to the destination
transaction.fiatToCrypto.failedCrypto delivery failed; see payload.error
Each event carries the full resource object under 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 source and destination each carry amountGross, amountNet, and a feesDeducted breakdown; the top-level rates object carries pair, exchangeRate, and effectiveRate. At launch all fee components are 0.00.
  • Gas sponsorship. sponsorGas defaults to true; OMS absorbs the destination gas. The absorbed cost shows as the top-level sponsorGasCost ("0" at launch).
  • Codes expire in 1 hour. Call POST /cash-ins/{cashInId}/refresh to issue a new code if the customer needs more time. Only works while status is pending.
  • 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, and cashIn.expired; the auto-created transaction fires transaction.fiatToCrypto.*. Subscribe to both, or read the cash-in with GET /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 sourceToDestination is cashToCrypto.
  • 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), or destination.wallet.externalAccount (a registered external account).