Skip to main content
A transaction is the record of one money movement from a source instrument to a destination instrument. Its top-level status is built for branching your code. subStatus is always present and adds operational detail for display and logging. Each movement of the money on a rail is a separate transfer with its own status.

Anatomy of a transaction

  • precursor names what created the transaction: quote, depositAddress, virtualAccount, cashIn, or walletDeposit (a crypto deposit straight into a custodial wallet, with no id).
  • source and destination are instruments. Bank instruments carry no rail; the rail is on the transfer.
  • failure appears only when status is failed, and hold only when status is awaitingAction.
There is no transaction type field. The corridor follows from the two instruments: Quotes report the corridor as sourceToDestination, and GET /transactions?sourceToDestination=... filters on it.

Status model

Branch on status. Use subStatus for display, logging, and support. A payout in flight never changes status: a withdrawal reads processing from creation until the money lands. completed and failed are final for almost every transaction. The exceptions are covered in Failed transactions and returns.

Holds

While status is awaitingAction, the hold object carries a code and a fixed message:

Sub-statuses

subStatus is a dotted string whose prefix is always the status. processing, completed, and failed each have a default (processing.initiated, completed.settled, failed.unspecified) used when nothing more specific applies. Every awaitingAction value names a specific blocking reason.
The list is open. OMS can add sub-statuses without an API version bump. If you receive a value you do not recognize, fall back to the prefix before the dot: an unknown processing.somethingNew still means processing. If your client validates against a generated enum, make it tolerant of new members.

processing

awaitingAction

completed

failed

failure.code gives the machine-readable reason (for example insufficientFunds, payoutFailed, or accountClosed) and failure.retryable tells you whether trying again can succeed. See ReasonCode in the API reference for every value.

Transfers

A transaction records what moved from where to where. Each movement of its money on one rail is a transfer (trf_ prefix). The transaction lists its transfers in creation order in transfers[], each as { id, role, rail, attempt }. The array is left out until the first transfer exists.
  • role is deposit (money coming in), payout (money going out to the destination), or refund (money sent back to the sender).
  • rail is how the money moved: ach, achSameDay, wire, internationalWire, rtp, bookTransfer, instant, intraBank, blockchain, card, or cash. instant is an RTP or FedNow payout, and the transfer names which one in instantRail. intraBank stays inside one bank, and the transfer names the same-bank rail in intraBankRail.
  • attempt is 1 for the first send. A payout sent again after a confirmed failure is a new transfer with the next attempt, and the failed one stays failed. A re-send of a payout whose outcome is unknown stays the same transfer.
GET /transfers/{transferId} returns the full transfer: status, amount, the from and to instruments, the ACH options it was sent with (secCode, companyDiscretionaryData), originatorName, and tracking. tracking holds the references that trace the transfer outside OMS (txHash, imad, uetr, endToEndId, instructionId) and is left out until OMS has one.

Failed transactions and returns

completed and failed can still move afterwards in a few cases:
  • A bank takes back money that settled. A returned payout or refund reads processing.underReview while OMS decides whether to resend it or send it back. A returned deposit reads failed.returnedToSender.
  • A failed deposit settles late. On a late provider report, the transaction returns to processing and continues to completed.
  • A refund starts after a failure. While it is in flight the transaction reads processing.returningToSender; once it settles, failed.returnedToSender.
When OMS has already received the inbound funds and the payout cannot complete, it returns them in the asset it received, with no action from you. The return is a refund transfer on the transaction.

Where returned funds go

Deposit addresses and virtual accounts accept an optional returnDestination at create or PATCH; send null in a PATCH to clear it.
  • For a deposit address, it is a crypto target: an OMS wallet (walletCrypto, which must be multi-asset, otherwise 422 ReturnDestinationMustBeMultiAsset) or a registered external wallet (walletExternal), plus a network that matches the network the address accepts.
  • For a virtual account, it is a registered bank account (bankUs, bankIban, or bankCanada) with a required rail in network. Only USD bank rails are accepted today: walletFiat, and bankCanada with network: "local", are rejected with 422 RailNotSupported.
Set a returnDestination on every deposit address and virtual account. Without one, returned crypto goes back to the onchain sender, which may be an exchange wallet the customer cannot receive on, and returned fiat is held for manual processing.

Webhook events

Each transaction and transfer change is delivered as a webhook, so you rarely need to poll GET /transactions/{transactionId}. The event’s data.resource is the transaction or transfer as GET returns it, so branch on data.resource.status. On statusChanged events, data.previousStatus names the status it left. A sub-status-only change still sends transaction.statusChanged, with previousStatus equal to status. Deliveries are at least once and can arrive out of order: deduplicate on the event id and order by createdAt per resource. See Receiving webhooks and the webhook events catalog.

Idempotency

POST requests that create a quote, transaction, wallet, or other money-movement resource require an Idempotency-Key header; the API reference marks it on each operation. Use one UUID per logical operation, tied to your internal order ID, and reuse it only to retry that same request. OMS returns the original response for a repeat within 24 hours. Reusing a key with a different body returns 422 IdempotencyKeyReuse.