> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polygon.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Virtual accounts

> Dedicated bank account numbers that auto-convert incoming fiat to crypto.

A virtual account gives a customer a dedicated bank account number. When fiat arrives via a supported rail, OMS automatically creates a transaction (`fiatAccountToCrypto`) and delivers crypto to the customer's configured wallet. No developer action is required after the account is provisioned.

Virtual accounts are persistent. OMS keeps them active until they are frozen, closed, or deleted.

<Note>
  Virtual accounts must be enabled for your project: OMS provisions the underlying deposit accounts with a virtual account provider configured during onboarding. To get set up, share your use case below.

  <Card title="Contact us" icon="envelope" href="https://polygon.technology/get-access?utm_source=docs&utm_medium=card&utm_campaign=oms_access">
    Tell us about your on-ramp flow and we'll enable virtual accounts for your project.
  </Card>
</Note>

## How it works

Once a virtual account is provisioned, you display its bank deposit instructions to the customer. When fiat arrives at that account, OMS:

1. Detects the inbound deposit on the bank rail.
2. Creates a transaction whose `precursor` is `{ "type": "virtualAccount", "id": "va_..." }`. Dereference the virtual account by that `id` for its bank routing block.
3. Moves the transaction directly to `processing` (there is no quote step, because pricing cannot be locked before the funds arrive).
4. Converts the deposited fiat to crypto and delivers it to the configured wallet.

The resulting transaction carries `sourceToDestination: "fiatAccountToCrypto"` and follows the standard [transaction lifecycle](/payments/core-concepts/transaction-lifecycle), including webhook events.

## Creating a virtual account

Create a virtual account with `POST /virtual-accounts`:

```json theme={null}
{
  "customerId": "cst_...",
  "source": { "asset": "usd", "network": "usBank" },
  "destination": {
    "type": "walletExternal",
    "details": { "id": "ext_wlt_...", "asset": "usdc", "network": "polygon" }
  },
  "accountHolder": "customer",
  "type": "bankUs",
  "label": "USD funding account"
}
```

| Field               | Required | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerId`        | Yes      | The customer the account belongs to.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `source`            | Yes      | The inbound fiat rail. `{ "asset": "usd", "network": "usBank" }` are the only supported values today.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `destination`       | Yes      | Where the inbound value is delivered. Two arms are supported today: `walletExternal`, a registered [external account](/payments/external-accounts) referenced by its `ext_wlt_` ID (OMS validates the resolved account's `asset` and `network`), and `walletFiat`, the customer's own OMS fiat wallet (holds inbound USD as a fiat balance; credited via an internal book transfer, no external rail). Raw on-chain addresses are not accepted. A `walletCrypto` (OMS crypto wallet) destination is rejected with `422 destinationWalletCryptoNotSupported`; register the OMS wallet as an external account and pass its `ext_wlt_` ID instead. |
| `returnDestination` | No       | Where to return an inbound deposit if the payout cannot be completed: a registered bank account (`bankUs`, `bankIban`, or `bankCanada`), each with a required rail in `network`. USD bank rails only today. When omitted, the funds are held for manual refund processing. See [failed transactions and returns](/payments/core-concepts/transaction-lifecycle#failed-transactions-and-returns).                                                                                                                                                                                                                                                |
| `accountHolder`     | Yes      | Must be `customer`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `type`              | Yes      | Must be `bankUs`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `bankMemo`          | No       | A wire/ACH memo the customer can include with deposits.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `sponsorGas`        | No       | When `true` (the default), OMS absorbs the on-chain gas cost for the destination delivery. Only `true` is currently supported.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `label`, `metadata` | No       | A display label and free-form string metadata.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

The `201` response returns the virtual account with `bankDetails: null`. OMS provisions the underlying deposit account asynchronously, and the account moves from `pending` to `active` once the bank details are ready.

<Note>
  An OMS-managed crypto wallet can back at most one active virtual account per project. A create that would share a wallet already held by another active virtual account is refused with `409 virtualAccountWalletInUse`. Delete the existing virtual account first, or point the new one at a different destination wallet.
</Note>

## Deposit instructions

The `bankDetails` field carries dual-rail bank deposit instructions to display in your UI. It is null until the underlying deposit account is provisioned. Both a domestic block and a SWIFT block are populated so a customer can fund the account from a US bank or from an international bank:

```json theme={null}
{
  "domestic": {
    "bankName": "OMS bank partner",
    "bankAddress": "...",
    "accountNumber": "123456789",
    "routingNumber": "021000021",
    "accountType": "checking",
    "network": ["ACH", "WIRE"],
    "beneficiary": { "name": "Customer name", "address": { "...": "..." } }
  },
  "swift": {
    "bankName": "Intermediary bank",
    "bankAddress": "...",
    "accountNumber": "...",
    "bic": "...",
    "memo": "FFC <customerName> <accountNumber>",
    "beneficiary": { "name": "OMS bank partner", "address": { "...": "..." } }
  }
}
```

On the domestic route, funds go directly to the OMS bank partner with the customer as the beneficiary. On the SWIFT route, funds route through a correspondent bank with the OMS bank partner as the beneficiary; the `memo` references the customer's provisioned account so the receiving bank credits the correct account.

## Third-party deposits

A virtual account is a named account: it carries the onboarded customer's own legal name, so inbound transfers are addressed to your customer, not to Polygon or your platform.

Senders do not need to be registered or identity-matched to the account holder. Any third party can push funds to the account over the supported rails, and each inbound deposit auto-creates its own transaction and fires its own events, so two deposits from two different senders arrive as two records. Every inbound payment is screened (AML and sanctions) before it converts.

Each deposit carries the originator details from the payment message: name, account and routing as sent, rail, amount, and reference. Wire and SWIFT payments carry the originator's name consistently; many ACH payments do not include it, so build reconciliation on your own reference and amount rather than on sender name alone.

## Provider and payout origin

Virtual account responses carry two flat reference fields alongside the customer summary:

```json theme={null}
"provider": "oms",
"payoutOrigin": { "type": "wallet", "id": "wlt_..." }
```

`provider` is the OMS legal-entity name for the deposit-account provider backing the underlying bank account. `payoutOrigin` mirrors the reference model used on transactions and identifies the OMS resource that receives converted funds: `type` is `wallet` for a crypto delivery, and the field is omitted for destination types where no OMS-owned payout resource applies. See [Party, precursor, and payout origin](/payments/transactions#party-precursor-and-payout-origin) for the shared shape.

## Customer summary

Virtual accounts carry a `customer` summary resolving the owning customer in one hop:

```json theme={null}
"customer": { "id": "cst_...", "name": "Jane Doe" }
```

The field is present on `GET /virtual-accounts/{virtualAccountId}`, on every entry of `GET /virtual-accounts`, and on the `POST /virtual-accounts` response. Its `destination` instrument (and `source` where applicable) also carries an opaque `displayName` you can render as-is. See [Customer summary on a transaction](/payments/transactions#customer-summary-on-a-transaction) and [Instrument display name](/payments/transactions#instrument-display-name) for the shared model.

## Listing and retrieving

`GET /virtual-accounts` lists virtual accounts 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`.

`GET /virtual-accounts/{virtualAccountId}` fetches a single virtual account by ID.

## Updating

`PATCH /virtual-accounts/{virtualAccountId}` accepts `destination` (re-point to a different `walletExternal` or `walletFiat` target of the same kind as the current destination), `returnDestination`, `sponsorGas`, `label`, and `metadata`; any other key in the body is rejected with `400`. A cross-kind switch involving `walletFiat` is rejected with `422 destinationTypeSwitchNotSupported`. A `PATCH` that re-points to `walletCrypto` is rejected with `422 destinationWalletCryptoNotSupported`.

Re-pointing `destination` to a healthy external account recovers a virtual account from `inactiveActionRequired` back to `active`. A re-point on an already `active` account updates the target without a status transition.

## Deleting

`DELETE /virtual-accounts/{virtualAccountId}` returns `202` and starts an asynchronous close of the underlying deposit account. The response echoes the virtual account with `deletionRequestedAt` set (`deletionRequestedBy` identifies the caller); the status stays as-is during the delete-pending window and finalizes to `deleted` once the underlying account closes. `finalBalance` snapshots the deposit account balance at the moment the account flips to `deleted`.

## Statuses

| Status                   | Meaning                                                                                                  |
| ------------------------ | -------------------------------------------------------------------------------------------------------- |
| `pending`                | Created; OMS is provisioning the underlying deposit account. `bankDetails` is null.                      |
| `active`                 | Provisioned and accepting deposits.                                                                      |
| `frozen`                 | Temporarily suspended; inbound deposits are not processed.                                               |
| `closed`                 | Permanently closed.                                                                                      |
| `deleted`                | Deletion finalized; the underlying deposit account is closed.                                            |
| `failed`                 | Provisioning failed; `failureReason` identifies the cause.                                               |
| `inactiveActionRequired` | The destination is no longer usable. Re-point `destination` to a healthy account to recover to `active`. |

## Simulating inbound deposits

In sandbox, you can simulate an inbound bank deposit to test your webhook and reconciliation flows without moving real funds. This endpoint is available in non-production environments only.

`POST /virtual-accounts/{virtualAccountId}/simulate/inbound-transfer` with a type-discriminated body. Set `type` to `bankUs` (with `network` set to `ach` or `wire`) for US domestic rails, or `bankIban` (with `network` set to `swift`) for international rails. Amounts are decimal strings in whole units:

```json theme={null}
{
  "type": "bankUs",
  "network": "ach",
  "asset": "usd",
  "amount": "50.00"
}
```

US domestic transfers (`ach`, `wire`) settle synchronously and return `submitted`. SWIFT transfers settle asynchronously with the upstream provider and return `pending` plus a `referenceId`:

```json theme={null}
{
  "virtualAccountId": "va_...",
  "type": "bankUs",
  "network": "ach",
  "asset": "usd",
  "amount": "50.00",
  "status": "submitted",
  "submittedAt": "2026-01-15T10:33:00Z",
  "referenceId": null
}
```

<Tip>
  Use the simulate endpoint to exercise the full auto-created transaction path in sandbox: the simulated inbound funds create a `fiatAccountToCrypto` transaction just as a real deposit would.
</Tip>

## Deposit address vs. virtual account

Both are persistent auto-route configurations. The difference is which side is fiat:

|                 | Deposit address        | Virtual account                 |
| --------------- | ---------------------- | ------------------------------- |
| Incoming funds  | Crypto (on-chain)      | Fiat (bank rail)                |
| Deposit details | On-chain inlet address | Bank account number and routing |
| Direction       | `cryptoToFiatAccount`  | `fiatAccountToCrypto`           |
| Destination     | Fiat account           | Crypto wallet                   |

## Related

* [Virtual accounts guide](/payments/guides/virtual-accounts): walkthrough of the inbound flow and webhook handling
* [Deposit addresses](/payments/deposit-addresses): the crypto equivalent for on-chain deposits
* [External accounts](/payments/external-accounts): registering an external wallet as a virtual account destination
* [Transaction lifecycle](/payments/core-concepts/transaction-lifecycle): statuses, sub-statuses, and webhook events for the auto-created transaction
* [Failed transactions and returns](/payments/core-concepts/transaction-lifecycle#failed-transactions-and-returns): where inbound deposits go when the payout cannot complete, and how to configure it
