Skip to main content
A deposit address is a reusable onchain address assigned to a customer. When crypto arrives at the address, OMS automatically creates and executes a transaction that delivers it to the configured destination: converted and paid out to a bank account or fiat wallet (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:
  1. Detects the inbound transfer on the source chain.
  2. Creates a transaction whose precursor is { "type": "depositAddress", "id": "da_..." }. Dereference the deposit address by that id for its inlet address and instructions.
  3. Moves the transaction directly to processing (there is no quote step, because pricing cannot be locked before the funds arrive).
  4. Delivers the funds to the configured destination, converting them to fiat first when the destination is a bank account or fiat wallet.
The resulting transaction follows the standard transaction lifecycle, including webhook events.

Creating a deposit address

Create a deposit address with POST /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

The depositInstructions 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.
OMS derives the field from the provider that issued the deposit address; you never set it. When the field is absent, the address accepts deposits from any onchain address. The field is also left out when OMS cannot identify the issuing provider, which is the case when 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:
  1. Collect the wallet address the funds will come from and its network family (evm, solana, or tron).
  2. Register it with POST /external-accounts as a walletExternal account 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.
  3. Share depositInstructions.address with the sender.
A deposit from an unregistered wallet still arrives, but OMS cannot attribute it. Its transaction moves to awaitingAction with subStatus: awaitingAction.awaitingSenderAttribution and a hold naming the wallet to register:
Registering a 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 the destination 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.
Each deposit creates a 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. Set destination.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.
The same 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 a bankUs 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 a payoutOrigin 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 a customer reference resolving the owning customer in one hop:
The field is present on 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 standard cryptoToFiatAccount 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: