cryptoToFiatAccount), or forwarded as crypto to a registered external wallet (cryptoToCrypto). No developer action is required after the address is provisioned, unless the address only accepts deposits from registered sender wallets (see Sender wallet registration).
Deposit addresses are persistent. OMS keeps them active until they are frozen or closed.
How it works
Once a deposit address is provisioned, you display its onchain address to the customer. When a supported stablecoin arrives at that address, OMS:- Detects the inbound transfer on the source chain.
- Creates a transaction whose
precursoris{ "type": "depositAddress", "id": "da_..." }. Dereference the deposit address by thatidfor its inlet address and instructions. - Moves the transaction directly to
processing(there is no quote step, because pricing cannot be locked before the funds arrive). - Delivers the funds to the configured destination, converting them to fiat first when the destination is a bank account or fiat wallet.
Creating a deposit address
Create a deposit address withPOST /deposit-addresses:
The
201 response returns the deposit address with depositInstructions carrying the accepted asset and network. The onchain address is left out until the provider issues it; a provider that issues it during the create call returns it in the 201 response, and otherwise the deposit address moves from pending to active once the address is issued.
Deposit instructions
ThedepositInstructions field carries the details to display in your UI. asset and network are present from creation, and address is present once the provider issues it:
Crypto sent to
address is delivered to the configured destination.
The OMS-managed wallet that backs a deposit address is reserved for that address’s auto-conversion flow. Referencing the same custodial wallet on the source or destination of a quote, a cash-in, a cash-out, or a debit-card push is refused with
422 walletBoundToDepositAddress. The wallet is also filtered out of a project-wide wallet listing so it does not appear as an independent balance. Use a different wallet for those flows.Sender wallet registration
Some deposit addresses accept a deposit only from a sender wallet you have registered in advance. The deposit address response tells you which kind you have:requiresCounterpartyWalletRegistration is present and true when each sender’s wallet must be registered as a walletExternal external account under the deposit address’s customer before it deposits.
provider is absent too. Read it from each deposit address rather than caching one value for your project.
When the field is true, capture the sender’s wallet before you share the deposit address:
- Collect the wallet address the funds will come from and its network family (
evm,solana, ortron). - Register it with
POST /external-accountsas awalletExternalaccount owned by the customer, or by one of the customer’s counterparties when someone else is sending. An EVM address registered once covers every EVM chain. - Share
depositInstructions.addresswith the sender.
awaitingAction with subStatus: awaitingAction.awaitingSenderAttribution and a hold naming the wallet to register:
walletExternal account for hold.match.address on hold.match.network releases the deposit, and the transaction continues to processing. Release is asynchronous; see Released sender-attribution holds. If no matching account is registered by hold.deadline, the transaction fails with failed.attributionTimeout.
Forward deposits to a wallet
A deposit address can forward inbound crypto to a wallet instead of a bank account. Set thedestination to a registered walletExternal external account, referenced by its ext_ ID. Raw blockchain addresses are not accepted, and a walletCrypto destination is rejected with 422 destinationWalletCryptoNotSupported.
cryptoToCrypto transaction that delivers the funds to the wallet. The destination network must be one the deposit address’s provider serves, or the create is rejected with 422 destinationNetworkUnsupported.
To forward deposits to a custodial wallet owned by another customer in your project, such as an omnibus account, register that wallet’s address as a walletExternal external account under the depositing customer and use its ext_ ID. OMS settles the delivery as an internal ledger movement, with no onchain transfer and no network fee.
Payout memo
A deposit address destination can carry an optional payment memo that OMS delivers to the beneficiary’s bank with each payout. Setdestination.details.memo on create or on a PATCH that replaces destination.
The published schema’s
paymentMemo pattern is the union of the ACH addenda and ISO 20022 character sets. The published pattern is deliberately permissive so a client validating locally will not reject a memo the server would have accepted; the narrower per-rail rules above are still enforced server-side on write, with a 422 on violation.
OMS reads the memo when it builds each payout, so an edit applies only to payouts built after it. A payout already sent keeps the memo it was built with. Set memo to null, omit it, or send only whitespace to use no memo of your own; the payout then carries the memo OMS composes for the rail, if any.
A memo outside its rail’s character set or length is rejected server-side with 422. The CAD local leg of bankCanada has no remittance field, so a non-empty memo there is rejected with 422 MemoNotSupported.
pattern and maxLength describe the normalized memo: the server trims leading and trailing whitespace before validating. A whitespace-only memo, or a 140-character memo wrapped in spaces, is accepted after trimming even though a strict local check against the raw string would reject it.memo field is available on virtual account bank destinations, which offer no rtp rail, and on quotes with a walletFiat source or with a walletCrypto source paying a bankUs destination. Any other quote corridor rejects a non-empty memo with 422 MemoNotSupported.
ACH company discretionary data
For abankUs destination with network: "ach" or network: "achSameDay", set an optional destination.details.companyDiscretionaryData. OMS carries the value in the NACHA batch header on every outbound ACH payout from the deposit address. This is the originator’s internal metadata and never reaches the beneficiary; it is distinct from memo, which does. Responses report the value as payout.companyDiscretionaryData.
Destination and payout rail
In responses,destination is the typed instrument deposits are paid out to, with type, id, asset, and a details block. A bank account destination carries no rail; the rail and ACH options are reported next to it in payout:
payout.rail is the rail each payout moves on, the same value as the payout transfer’s rail. On ach and achSameDay, payout also carries secCode, derived from the destination owner’s and the customer’s entity type and left out while either is unknown, and companyDiscretionaryData when you set it. A crypto destination has no payout.
Payout origin
Deposit address responses carry apayoutOrigin on both the top-level body and each v1 webhook payload. It describes whose name a payout from the address is sent under: the holder stored when the deposit address was created, with the customer’s legal name resolved onto accountHolderName where applicable. A walletFiat destination or an address with no payout destination carries no payoutOrigin. See the shared payout origin shape.
Customer reference
Deposit addresses carry acustomer reference resolving the owning customer in one hop:
GET /deposit-addresses/{depositAddressId}, on every entry of GET /deposit-addresses, on POST /deposit-addresses and PATCH /deposit-addresses/{depositAddressId} responses, and on the deposit-address webhook payloads. Read the customer’s name and status from the customer resource. The destination is a typed instrument; render it from its details block rather than from a pre-formatted label. See Rendering an instrument and the Customer reference shared model.
Listing and retrieving
GET /deposit-addresses lists deposit addresses across every customer in your organization. Both filters are optional: customerId scopes the list to one customer, and status to one lifecycle state. Paginate with limit, startingAfter, and endingBefore; each page returns nextCursor, previousCursor, and hasMore.
Pagination now orders pages on
(created_at, id) and emits an opaque cursor instead of a da_-prefixed ID. Pass the nextCursor or previousCursor from one page back as startingAfter or endingBefore on the next request; do not parse the cursor. A stored da_-prefixed token from a previous release is still accepted on startingAfter for one release window. endingBefore on an opaque cursor returns the adjacent newer page, not the newest n rows in the account.GET /deposit-addresses/{depositAddressId} fetches a single deposit address by ID.
Updating
PATCH /deposit-addresses/{depositAddressId} accepts destination (re-point to a different destination), returnDestination, label, metadata, and sponsorGas; any other key in the body is rejected with 400.
Re-pointing 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 transition.
Deposit address responses also carry a read-only returnDestinationTypes array that lists the walletCrypto and walletExternal return targets this address accepts, derived from the issuing provider and depositInstructions.network. An empty array means no return destination can be configured. The field is on API responses only; it is not included in the depositAddress.statusChanged event.
A deposit address is permanent: there is no delete endpoint. The onchain address is published to senders and can receive a deposit at any time, so removing the record would strand any funds that arrive afterwards with nothing to route them to. The terminal closed status is reached through the provider’s own lifecycle or an operator action, and a closed address stays retrievable by ID. To stop using one, stop handing its address to senders.
Statuses
Simulating inbound deposits
Sandbox deposit addresses accept real testnet transfers. Fund the address’s onchain inlet from a testnet faucet (for example, Circle’s testnet USDC faucet on Sepolia), and OMS reacts to the onchain confirmation just as it would to a mainnet deposit: the standardcryptoToFiatAccount transaction is auto-created and progresses through the transaction lifecycle, firing the same webhook events.
There is no sandbox-only simulation endpoint for a deposit-address inbound. The fiat sibling on virtual accounts (see Simulating inbound deposits) still exists because bank rails have no public testnet.
Deposit address vs. virtual account
Both are persistent auto-route configurations. The difference is which side is fiat:Related
- Deposit addresses guide: walkthrough of the inbound flow and webhook handling
- Virtual accounts: the fiat equivalent, a bank account number that auto-converts to crypto
- External accounts: registering the bank account or wallet a deposit address delivers to
- Transaction lifecycle: statuses, sub-statuses, and webhook events for the auto-created transaction
- Failed transactions and returns: where inbound crypto goes when the payout cannot complete, and how to configure it