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 {token} on every request. Every mutating request (POST and PATCH) also requires an Idempotency-Key header. See Get started for the full flow.Deposit addresses must be enabled for your project before
POST /deposit-addresses succeeds, and the customer must be provisioned for them. Contact us to enable deposit addresses for your project.Contact us
Share your on-ramp use case and we’ll enable deposit addresses for your project.
cryptoToFiatAccount transaction that converts it and pays out to the customer’s registered bank account. There is no quote step and no amount specified upfront: the amount is whatever the sender deposits.
Prerequisites
Before you can create a deposit address, you need:- A customer with a
cst_ID and thecryptoCustodyendorsement active. - A registered bank external account owned by that customer to receive the payout. Register one with
POST /external-accounts(typeofbankUs,bankIban, orbankCanada); the account’sext_ID goes in the deposit address’sdestination. - Deposit addresses enabled for your project and the customer provisioned for them (contact us).
- A webhook subscription covering the
transaction.cryptoToFiat.*events (and, optionally, thedepositAddress.*lifecycle events) so you learn when the auto-created transaction is delivered. Register one withPOST /webhooks(body{ url, events }) or in the OMS Dashboard. See the transaction lifecycle for the delivery model.
Create a deposit address
Create the address withPOST /deposit-addresses. You name the customer, the inbound asset and network the address watches, and the registered bank external account that receives the converted funds.
customerId: the customer who owns the address. Required.expectedSourceAsset: the inbound stablecoin the address expects,usdcorusdt(lowercase). Required.expectedSourceNetwork: the network the address watches for deposits, for examplebase,ethereum, orsolana. Required.destination: the registered bank external account that receives the payout. Required.typeisbankUs,bankIban, orbankCanada, anddetailscarries the account’sid(for exampleext_bankUs_...),asset,network, andaccountHolder.accountHoldermust becustomer. OMS validates thedetailsagainst the resolved external account.sponsorGas: whentrue, OMS absorbs the on-chain gas cost of the destination delivery. Optional, defaults totrue; onlytrueis currently supported.labelandmetadata: an optional display label and an optional string-to-string map for your own references.
201 response returns the deposit address with a da_ ID:
depositInstructions is null in the 201 response: OMS provisions the on-chain inlet address asynchronously. transactionType is always cryptoToFiat; the transactions the address produces report sourceToDestination: cryptoToFiatAccount.
Wait for provisioning, then share the address
PollGET /deposit-addresses/{depositAddressId} (or re-fetch the address before you display it) until status is active and depositInstructions is populated:
active, depositInstructions carries the inlet address to display to your customer:
| Field | Meaning |
|---|---|
address | The OMS-owned on-chain inlet address for this deposit address. Give this to your customer. |
asset | The stablecoin the address accepts. Same value as expectedSourceAsset. |
network | The chain the address accepts funds on. Same value as expectedSourceNetwork. |
expiresAt | Reserved for a future provider-imposed inlet expiry. Null today. |
address is converted and paid out to the configured bank external account.
Deposit-address webhooks are live. The address itself fires lifecycle events (
depositAddress.created, depositAddress.active, depositAddress.paused, depositAddress.resumed, depositAddress.frozen, depositAddress.closed, depositAddress.deleted, depositAddress.failed); each inbound deposit fires deposit_address.crypto_deposit.* events; and the payout leg fires deposit_address.ach_payout.*, deposit_address.wire_payout.*, or deposit_address.intl_wire_payout.* events. Subscribe to depositAddress.active to learn when the inlet address is ready instead of polling. See Webhook events for the full catalog.Test in sandbox
In sandbox, simulate an inbound transfer to exercise the auto-created transaction path without moving real funds. CallPOST /deposit-addresses/{depositAddressId}/simulate with the amount to simulate. The currency and network are resolved server-side from the deposit address. This endpoint returns 404 in production.
value is the stablecoin amount in cents (greater than 0 and at most 100000). The response echoes the simulated deposit and returns a synthetic transactionHash you can correlate against the webhook:
What OMS creates when funds arrive
When OMS detects the deposit (real or simulated), it creates a transaction inprocessing status and fires the transaction.cryptoToFiat.processing event, with the full transaction under payload. The transaction skips the quote step, so its precursor is typed depositAddress and carries the depositAddressId. Pricing is calculated at the moment funds arrive and lives in the top-level pricing object.
The transaction’s sourceToDestination is cryptoToFiatAccount: the source is the sender’s on-chain transfer and the destination is the deposit address’s configured bank external account.
precursor.typeisdepositAddress, andprecursor.details.depositAddressIdlinks the transaction back to the originating deposit address.source.details.txHashis the on-chain hash of the incoming deposit.destinationis the bank external account you configured on the deposit address.pricing.source.amountGrossis the amount actually deposited, now known.pricing.source.feesDeducted.developeris your fee, computed as a percentage of the deposit.fixedAmountSideissource: the deposited amount is fixed and the destination payout is calculated from it.
Track the transaction
Branch on the transactionstatus. processing means the deposit was detected and execution is underway; completed means funds were delivered; failed is a terminal failure with an error object. See the transaction lifecycle for the full status model and sub-statuses.
Prefer webhooks over polling: OMS fires transaction.cryptoToFiat.processing, transaction.cryptoToFiat.completed, and transaction.cryptoToFiat.failed for the auto-created transaction, and each delivery carries the full transaction object under payload, so your handler branches on the event name or payload.status. If you do poll, read the transaction directly:
Reuse
A deposit address is persistent. While it isactive it keeps monitoring its inlet address, so every subsequent deposit triggers the same flow: a new transaction with a new txn_ ID, the same depositAddressId, and pricing computed from the same configuration. There is no limit on the number of transactions a single deposit address can produce.
Status lifecycle
| Status | Meaning |
|---|---|
pending | Created; OMS is provisioning the inlet address. depositInstructions is still null. |
active | The inlet address is live. Deposits convert and pay out to the destination. |
frozen | Deposits are suspended. statusReason explains why. |
closed | The address is permanently closed and no longer monitors its inlet address. |
failed | Provisioning failed; failureReason identifies the category. Create a new deposit address. |
inactiveActionRequired | The destination external account is no longer usable. Re-point destination with PATCH to recover to active. |
Held deposits
A deposit can arrive in a state the transaction cannot settle from. Instead of failing, the auto-created transaction moves toawaitingAction with a typed hold that explains what is blocking it and, when a deadline applies, how long you have to resolve it. A deposit held for sender attribution also fires the deposit_address.crypto_deposit.needs_attribution event:
hold.type | Cause | Resolution |
|---|---|---|
senderAttribution | The deposit came from an on-chain address OMS cannot attribute to a known sender. The hold carries the txHash and matchableExternalAccountCriteria (the address and network family to match). | Register a walletExternal external account matching the criteria. The registration’s create response lists the released transactions in resolvedTransactions, and each moves back to processing once the provider confirms settlement. |
depositAddressFrozen | The deposit arrived while the deposit address was frozen. | Clears when the freeze lifts; no developer action. |
depositAddressInactive | The destination external account became unusable (see hold.cause for the account, its status, and the reason). | Re-point the deposit address destination to a healthy external account with PATCH. |
failed.attributionTimeout, failed.depositAddressFrozenTimeout, failed.depositAddressInactiveTimeout). Branch on status; use subStatus and hold for operational detail.
Manage deposit addresses
List
GET /deposit-addresses spans every customer in your organization. Filter with customerId and status, both optional. Paginate with limit, startingAfter, and endingBefore:
{ object, data, hasMore, nextCursor, previousCursor }. Pass nextCursor as startingAfter to fetch the next page, or previousCursor as endingBefore to page backward; hasMore signals whether more rows exist in the direction of travel.
Update
PATCH /deposit-addresses/{depositAddressId} accepts destination, label, and metadata; sponsorGas is also accepted, but only true is supported. Any other key in the body is rejected with 400.
destination to a healthy bank external account recovers a deposit address from inactiveActionRequired back to active. A re-point on an already active address updates the target without a status change. The new destination is validated exactly like create.
No delete
There is noDELETE endpoint for deposit addresses. If you no longer want deposits on an address, stop sharing its inlet address.
Related
- Deposit addresses overview: concept summary and comparison with virtual accounts
- Virtual accounts guide: the fiat equivalent, a bank account number that auto-converts to crypto
- Transaction lifecycle: statuses, sub-statuses, and webhook events for the auto-created transaction