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 POST that creates or changes a resource also requires an Idempotency-Key header. See Get started for the full flow.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.
This guide walks through the bank payout. A deposit address can also forward crypto to a registered wallet; see Forward deposits to a wallet.
Prerequisites
Before you can create a deposit address, you need:- A customer with a
cst_ID, provisioned with the banking provider for deposit addresses. For a bank or fiat wallet destination, the customer also needs theusdendorsementactive. - A registered destination external account to receive the funds. For a bank payout, register a bank account owned by the customer with
POST /external-accounts(typeofbankUs,bankIban, orbankCanada); to forward crypto, register awalletExternalaccount. The account’sext_ID goes in the deposit address’sdestination. - Deposit addresses enabled for your project.
GET /networkslists the networks your project can receive on. - A webhook subscription covering
depositAddress.statusChangedandtransaction.statusChanged, so you learn when the address is ready and when each deposit is delivered. See Receiving webhooks.
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. Availability depends on the provider that issues the address; an unsupported network is rejected with422 ExpectedSourceNetworkUnsupported.destination: the registered external account that receives the funds. Required. For a bank payout,typeisbankUs,bankIban, orbankCanada, anddetailscarries the account’sid(anext_ID),asset, andnetwork(ach,achSameDay,wire, orrtpforbankUs). OMS validates thedetailsagainst the resolved external account. To hold the USD in the customer’s OMS fiat wallet, usewalletFiatwith itswlt_ID. To forward crypto instead, use awalletExternaldestination; see Forward deposits to a wallet.destination.details.memo: optional payment memo delivered to the beneficiary’s bank with each payout. Honored onbankUsachandachSameDay(ACH addenda, max 80 characters, uppercase NACHA set),bankUswireandrtp(max 140 characters), andbankIbanorbankCanadaonswift(max 140 characters). The wire, RTP, and SWIFT character set is letters, digits, space, and/?:().,'+-. Edits apply only to payouts built after the edit. Sendnullto use no memo of your own. A non-empty value on the CADlocalleg ofbankCanadais rejected with422 MemoNotSupported. See Payout memo.destination.details.companyDiscretionaryData: optional ACH-only field for the originator’s internal use, carried verbatim in the NACHA batch header. Honored on abankUsdestination withnetwork: "ach"ornetwork: "achSameDay". Max 20 characters; uppercase NACHA character set (A-Z,0-9, space, and& - . $ * / # @ %). Rejected with422 MemoNotSupportedonbankUswireandrtp. Distinct frommemo: this never reaches the beneficiary. Responses report it aspayout.companyDiscretionaryData.returnDestination: optional. A registered crypto wallet for returning stranded deposits:{ "type": "walletCrypto" | "walletExternal", "network", "id" }, wherenetworkmust equalexpectedSourceNetwork.sponsorGas: whentrue, OMS absorbs the onchain gas cost of the destination delivery. 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 carries the asset and network the address accepts from creation. Its address is left out until the provider issues it, unless the provider issues it during the create call. The bank destination carries no rail: the rail you chose and its ACH options are reported in payout.
Wait for provisioning, then share the address
Wait fordepositAddress.statusChanged with data.resource.status of active, or poll GET /deposit-addresses/{depositAddressId} until status is active and depositInstructions.address is present:
active, depositInstructions carries the onchain address to display to your customer:
Crypto sent to
address is delivered to the configured destination: converted and paid out for a bank account, or forwarded as crypto for a wallet.
What OMS creates when funds arrive
When OMS detects an inbound deposit, it creates a transaction inprocessing and sends transaction.created. The onchain deposit and the payout are separate transfers, each reported through transfer.created and transfer.statusChanged. The transaction skips the quote step, so its precursor is { "type": "depositAddress", "id": "da_..." }; dereference the deposit address by that id for its inlet and instructions. Pricing is calculated at the moment funds arrive and lives in the top-level pricing object.
The transaction’s source is the sender’s onchain transfer and its destination is the deposit address’s configured bank external account.
precursoris{ "type": "depositAddress", "id": "da_..." }; dereference the deposit address by thatidfor its inlet address and instructions.transferslists each movement of the transaction’s money. The onchain hash of the incoming deposit is on thedeposittransfer: fetch it withGET /transfers/{transferId}and readtracking.txHash.destinationis the bank external account you configured on the deposit address. It carries no rail; the rail and ACH options are on the payout transfer, which joinstransfersonce it exists. The source is the onchain sender;source.details.addresscarries the originating address verbatim.pricing.source.amountis the amount actually deposited, now known. Amounts are padded to each asset’s precision.pricing.feeslists your developer fee, computed as a percentage of the deposit.fixedAmountSideissource: the deposited amount is fixed and the destination payout is calculated from it.
Forward deposits to a wallet
To forward inbound crypto to a wallet instead of paying out to a bank, set thedestination to a registered walletExternal external account, referenced by its ext_ ID:
cryptoToCrypto transaction that delivers the funds to the wallet, with no conversion to fiat. Raw blockchain addresses are not accepted as a destination, and a walletCrypto destination is rejected with 422 DestinationWalletCryptoNotSupported: register the wallet as an external account first. The wallet’s network must be one the address’s provider serves, or the request fails with 422 DestinationNetworkUnsupported.
The destination can be a custodial wallet owned by another customer in your project. Register that wallet’s address as a walletExternal external account under the depositing customer, owned by a counterparty that represents the wallet’s owner, and use its ext_ ID. This is how you automatically sweep each customer’s deposits into an Omnibus account.
A deposit address accepts one asset on one network. To forward both USDC and USDT, create one deposit address per asset, each pointing at a wallet that holds the matching asset.
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 a failure object whose code, message, and retryable fields say what went wrong. See the transaction lifecycle for the full status model and sub-statuses.
Prefer webhooks over polling: OMS sends transaction.statusChanged on each transition of the auto-created transaction, with the full transaction under data.resource, so your handler branches on data.resource.status. If you do poll, read the transaction directly, or scope a listing to the deposit address:
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
Held deposits
A deposit can arrive in a state the transaction cannot settle from. Instead of failing, the auto-created transaction moves toawaitingAction, and its subStatus names what is blocking it. When you must act, a hold carries a code and a fixed message. Each move into and out of a hold sends transaction.statusChanged:
A held deposit that is never resolved fails with a matching terminal sub-status (
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, returnDestination (send null to clear it), 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 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