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

# Virtual Accounts

> A Virtual Account is a dedicated bank account that automatically converts fiat deposits into stablecoins. When users send money to a virtual account via bank transfer, the funds are instantly converted to stablecoins and delivered to their wallet.

## How Virtual Accounts Work

Virtual accounts enable fiat-to-stablecoin onramps through a simple three-step process:

<Steps>
  <Step title="Create the account">
    Generate a unique bank account number for the user.
  </Step>

  <Step title="Deposits fiat">User receives funds via ACH, wire, or RTP.</Step>

  <Step title="Automatic conversion">
    Fiat is automatically converted to stablecoins and delivered to the user’s
    wallet.
  </Step>
</Steps>

Each virtual account is configured for a specific conversion pair (e.g., USD → USDC on Polygon). You can create multiple virtual accounts per user for different currencies and chains.

## Eligibility and Enablement

USD virtual accounts must be enabled for your profile before you can create one for a user. Business users incorporated in India, Nigeria, Panama, the Philippines, Saudi Arabia, or Vietnam require enhanced verification before they are eligible.

For these businesses:

1. Provide every UBO with 10% ownership or above during KYB.
2. Retrieve the business's KYC status and find each UBO whose status is `INCOMPLETE`.
3. Send each UBO their own `liveness` URL from the response so they can complete the hosted identity and liveness check.
4. Wait until no required UBO remains incomplete and HIFI has enabled virtual accounts for the profile before creating the account.

<Info>
  This process enables USD virtual accounts only. It does not expand USD SWIFT
  payout coverage. See the [Liveness Checks
  guide](/guides/liveness-checks) for the API response and implementation flow.
</Info>

## Creating Virtual Accounts

Create a virtual account using the [Create Virtual Account](https://docs.hifi.com/api-reference/virtual-account/create-a-virtual-account) endpoint.

**Request:**

```bash theme={null}
curl -X POST "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceCurrency": "usd",
    "destinationCurrency": "usdc",
    "destinationChain": "POLYGON"
  }'
```

**Request Fields:**

<ResponseField name="sourceCurrency" type="string" required>
  Fiat currency to accept. Currently only `usd` is supported.
</ResponseField>

<ResponseField name="destinationCurrency" type="string" required>
  Stablecoin to receive after conversion. Options: `usdc`, `usdt`
</ResponseField>

<ResponseField name="destinationChain" type="string" required>
  Blockchain where stablecoins will be sent. Options: `POLYGON`, `ETHEREUM`,
  `BASE`
</ResponseField>

<ResponseField name="externalWalletId" type="string">
  Optional. If provided, stablecoins are sent to this external wallet instead of
  the user's HIFI wallet.
</ResponseField>

<Warning>
  **USD Only:** Virtual accounts currently support USD deposits only. For other
  currencies, use the [Onramp
  API](https://docs.hifi.com/api-reference/onramp/create-an-onramp).
</Warning>

**Response:**

```json theme={null}
{
  "message": "Virtual account created successfully",
  "accountInfo": {
    "id": "va_abc123",
    "userId": "usr_abc123",
    "source": {
      "paymentRail": ["ach", "wire", "rtp"],
      "currency": "usd"
    },
    "destination": {
      "chain": "POLYGON",
      "currency": "usdc",
      "walletAddress": "0xd102C4130985B7fcB95697616eaf5542c4f98d49",
      "externalWalletId": null
    },
    "status": "activated",
    "depositInstructions": {
      "bankName": "Cross River Bank",
      "bankAddress": "885 Teaneck Road, Teaneck, NJ 07666",
      "beneficiary": {
        "name": "John Doe",
        "address": "123 Main St, New York, NY, 10010, US"
      },
      "ach": {
        "routingNumber": "021214891",
        "accountNumber": "344176915009"
      },
      "wire": {
        "routingNumber": "021214891",
        "accountNumber": "344176915009"
      },
      "rtp": {
        "routingNumber": "021214891",
        "accountNumber": "344176915009"
      }
    }
  }
}
```

**Response Fields:**

<ResponseField name="accountInfo.id" type="string">
  Unique virtual account ID. **Save this** for managing the account and
  monitoring deposits.
</ResponseField>

<ResponseField name="accountInfo.status" type="string">
  Account status. `activated` means ready to receive deposits.
</ResponseField>

<ResponseField name="accountInfo.source" type="object">
  Source configuration for the virtual account.

  <Expandable title="properties">
    <ResponseField name="paymentRail" type="array">
      Supported payment methods: `ach`, `wire`, `rtp`
    </ResponseField>

    <ResponseField name="currency" type="string">
      Fiat currency accepted
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="accountInfo.destination" type="object">
  Destination configuration for converted stablecoins.

  <Expandable title="properties">
    <ResponseField name="chain" type="string">
      Blockchain where stablecoins will be sent
    </ResponseField>

    <ResponseField name="currency" type="string">
      Stablecoin type
    </ResponseField>

    <ResponseField name="walletAddress" type="string">
      Wallet address where stablecoins will be deposited
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="accountInfo.depositInstructions" type="object">
  **Most Important:** Bank account details for users to send deposits. Share
  these instructions with your users.

  <Expandable title="properties">
    <ResponseField name="bankName" type="string">
      Bank holding the virtual account
    </ResponseField>

    <ResponseField name="bankAddress" type="string">
      Physical bank address
    </ResponseField>

    <ResponseField name="beneficiary" type="object">
      <Expandable title="properties">
        <ResponseField name="name" type="string">
          Beneficiary name (matches user's name for compliance)
        </ResponseField>

        <ResponseField name="address" type="string">
          Beneficiary address
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="ach" type="object">
      <Expandable title="properties">
        <ResponseField name="routingNumber" type="string">
          ACH routing number
        </ResponseField>

        <ResponseField name="accountNumber" type="string">
          Account number for ACH deposits
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="wire" type="object">
      <Expandable title="properties">
        <ResponseField name="routingNumber" type="string">
          Wire routing number
        </ResponseField>

        <ResponseField name="accountNumber" type="string">
          Account number for wire deposits
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="rtp" type="object">
      <Expandable title="properties">
        <ResponseField name="routingNumber" type="string">
          RTP routing number
        </ResponseField>

        <ResponseField name="accountNumber" type="string">
          Account number for RTP deposits
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Payment Rails

Virtual accounts support multiple payment methods to accommodate different use cases:

| Payment Rail | Speed             | Cost   | Use Case                          |
| :----------- | :---------------- | :----- | :-------------------------------- |
| **ACH**      | 1-3 business days | Low    | Domestic US bank transfers        |
| **Wire**     | Same day          | Medium | Larger amounts, faster processing |
| **RTP**      | Real-time         | Medium | Instant transfers                 |

Users can deposit via any supported rail using the same account number. The conversion and delivery happens automatically regardless of which payment method is used.

## Deposit Processing

When a deposit is detected, HIFI automatically:

1. **Validates the deposit** - Confirms amount and sender details
2. **Converts to stablecoin** - Exchanges fiat for stablecoins at current rates
3. **Sends to wallet** - Delivers stablecoins to the configured destination
4. **Triggers webhooks** - Sends `ONRAMP.CREATE` and `ONRAMP.UPDATE` events

Processing times vary by payment rail:

* **ACH**: 1-3 business days for funds to clear, then instant conversion
* **Wire**: Same-day processing, then instant conversion
* **RTP**: Real-time processing and instant conversion

<Info>
  **Webhook Notifications:** Subscribe to `ONRAMP.CREATE` and `ONRAMP.UPDATE`
  events to track deposits in real-time. See [Webhooks](/docs/webhooks) for
  setup instructions.
</Info>

## Tracking Onramps

When deposits arrive at a virtual account, HIFI creates an onramp transaction to handle the conversion. You can see which virtual account was used by checking the onramp's `virtualAccountId` field.

**List onramps for a user:**

```bash theme={null}
curl -X GET "https://sandbox.hifibridge.com/v2/onramps?userId=usr_abc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response (excerpt):**

```json theme={null}
{
  "count": 1,
  "records": [
    {
      "transferType": "ONRAMP",
      "transferDetails": {
        "id": "onr_xyz789",
        "status": "COMPLETED",
        "source": {
          "currency": "usd",
          "amount": 100
        },
        "destination": {
          "currency": "usdc",
          "chain": "POLYGON",
          "amount": 100
        },
        "virtualAccountId": "va_abc123",
        "receipt": {
          "transactionHash": "0xe5284c4cb35ae9b5eb0ae23b840032f320b87b63f8967ee9b67ee09dfe6a194a"
        }
      }
    }
  ]
}
```

**Key Fields:**

<ResponseField name="transferDetails.virtualAccountId" type="string">
  The virtual account ID that received the deposit. If `null`, this onramp was
  created through a different method (not via virtual account).
</ResponseField>

<ResponseField name="transferDetails.status" type="string">
  Onramp status. See [Onramps](/docs/transactions/onramps) for complete status
  documentation.
</ResponseField>

<ResponseField name="transferDetails.receipt.transactionHash" type="string">
  Blockchain transaction hash (available when status is COMPLETED)
</ResponseField>

## Managing Virtual Accounts

### Update Configuration

Change the destination chain or currency for an existing virtual account using the [Update Virtual Account](https://docs.hifi.com/api-reference/virtual-account/update-a-virtual-account) endpoint.

**Request:**

```bash theme={null}
curl -X PATCH "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts/va_abc123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destinationCurrency": "usdt",
    "destinationChain": "ETHEREUM"
  }'
```

<Note>
  Updating the configuration only affects future deposits. Existing pending
  deposits will complete with the original configuration.
</Note>

### Deactivate Account

Temporarily disable a virtual account to prevent new deposits using the [Deactivate Virtual Account](https://docs.hifi.com/api-reference/virtual-account/deactivate-a-virtual-account) endpoint.

**Request:**

```bash theme={null}
curl -X POST "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts/va_abc123/deactivate" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

When deactivated:

* New deposits are rejected
* Existing pending deposits continue processing
* Account can be reactivated later

**Reactivate:**

```bash theme={null}
curl -X POST "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts/va_abc123/activate" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Microdeposits

When you fetch a virtual account, the response includes a `microDeposits` object containing information about small payments often used for account confirmation. These microdeposits help verify bank account ownership.

**Fetch a virtual account:**

```bash theme={null}
curl -X GET "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts/va_abc123" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response (excerpt showing microDeposits):**

```json theme={null}
{
  "id": "va_abc123",
  "userId": "usr_abc123",
  "status": "activated",
  "microDeposits": {
    "count": 2,
    "data": [
      {
        "createdAt": "2024-01-15T10:30:00Z",
        "currency": "usd",
        "amount": 1,
        "sourceBankInfo": {
          "bankName": "Chase Bank",
          "senderName": "John Doe",
          "routingNumber": "021000021",
          "accountNumber": "516843515316",
          "accountType": "checking",
          "fedBatchId": "20240115-001",
          "imad": "20240115103000001",
          "omad": "20240115103000002",
          "traceNumber": "123456789012",
          "bankAddress": "270 Park Avenue, New York, NY 10017",
          "description": "Microdeposit verification",
          "paymentRail": "ach"
        }
      },
      {
        "createdAt": "2024-01-15T10:31:00Z",
        "currency": "usd",
        "amount": 2,
        "sourceBankInfo": {
          "bankName": "Chase Bank",
          "senderName": "John Doe",
          "routingNumber": "021000021",
          "accountNumber": "516843515316",
          "accountType": "checking",
          "fedBatchId": "20240115-002",
          "imad": "20240115103100001",
          "omad": "20240115103100002",
          "traceNumber": "123456789013",
          "bankAddress": "270 Park Avenue, New York, NY 10017",
          "description": "Microdeposit verification",
          "paymentRail": "ach"
        }
      }
    ]
  }
}
```

<Expandable title="microDeposits Response Fields">
  <ResponseField name="microDeposits.count" type="integer">
    Total number of microdeposits recorded for this virtual account.
  </ResponseField>

  <ResponseField name="microDeposits.data" type="array">
    Array of microdeposit records.

    <Expandable title="data item properties">
      <ResponseField name="createdAt" type="string">
        Timestamp when the microdeposit was created (ISO 8601 format).
      </ResponseField>

      <ResponseField name="currency" type="string">
        Currency of the microdeposit (typically `usd`).
      </ResponseField>

      <ResponseField name="amount" type="integer">
        Amount of the microdeposit in cents. Common values are 1-2 cents for verification purposes.
      </ResponseField>

      <ResponseField name="sourceBankInfo" type="object">
        Information about the source bank account that sent the microdeposit.

        <Expandable title="sourceBankInfo properties">
          <ResponseField name="bankName" type="string">
            Name of the bank that sent the microdeposit.
          </ResponseField>

          <ResponseField name="senderName" type="string">
            Name of the account holder who sent the microdeposit.
          </ResponseField>

          <ResponseField name="routingNumber" type="string">
            Bank routing number of the source account.
          </ResponseField>

          <ResponseField name="accountNumber" type="string">
            Account number of the source account (may be partially masked).
          </ResponseField>

          <ResponseField name="accountType" type="string">
            Type of account (e.g., `checking`, `savings`).
          </ResponseField>

          <ResponseField name="fedBatchId" type="string">
            Federal batch identifier for the microdeposit transaction.
          </ResponseField>

          <ResponseField name="imad" type="string">
            Incoming Message Authentication Data (IMAD) for ACH transactions.
          </ResponseField>

          <ResponseField name="omad" type="string">
            Outgoing Message Authentication Data (OMAD) for ACH transactions.
          </ResponseField>

          <ResponseField name="traceNumber" type="string">
            Trace number used to identify and track the transaction.
          </ResponseField>

          <ResponseField name="bankAddress" type="string">
            Physical address of the source bank.
          </ResponseField>

          <ResponseField name="description" type="string">
            Description or memo associated with the microdeposit transaction.
          </ResponseField>

          <ResponseField name="paymentRail" type="string">
            Payment method used for the microdeposit. One of: `ach`, `wire`, `rtp`.
          </ResponseField>
        </Expandable>
      </ResponseField>
    </Expandable>
  </ResponseField>
</Expandable>

<Note>
  **Dashboard Access:** When you open a virtual account in the dashboard, you
  can find microdeposits information displayed alongside other account details.
</Note>

## Key Concepts

<AccordionGroup>
  <Accordion title="One Account Per Configuration">
    Each virtual account is tied to a specific conversion pair (source currency + destination currency + destination chain). To support multiple conversion options, create separate virtual accounts.

    Example: If you need both USD→USDC on Polygon and USD→USDT on Ethereum, create two virtual accounts.
  </Accordion>

  {" "}

  <Accordion title="Beneficiary Name Matching">
    The beneficiary name on the virtual account must match the user's KYC name.
    Deposits from accounts with mismatched names may be rejected for compliance
    reasons.
  </Accordion>

  {" "}

  <Accordion title="USDT Fees">
    USDT conversions incur an additional 0.1% exchange fee beyond standard
    conversion rates. This fee appears on your monthly invoice.
  </Accordion>

  <Accordion title="External Wallets">
    By default, stablecoins are sent to the user's HIFI wallet. Use the `externalWalletId` parameter to send to an external wallet address instead. The external wallet must be pre-registered via the [External Wallets API](https://docs.hifi.com/api-reference/external-wallet/create-external-wallet).

    <Note>
      **Registration Required:** Unlike regular wallet transfers (which can send directly to any address), virtual accounts require external wallets to be registered first when using the `externalWalletId` parameter. If you prefer not to register, create a standard virtual account without `externalWalletId` and then transfer funds to the external wallet using the [Wallet Transfer API](/docs/wallets).
    </Note>
  </Accordion>
</AccordionGroup>

## Sandbox Testing

In sandbox, simulate deposits without real bank transfers using the [Simulate Deposit](https://docs.hifi.com/api-reference/virtual-account/simulate-a-deposit-to-a-virtual-account) endpoint.

**Request:**

```bash theme={null}
curl -X POST "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts/va_abc123/simulate-deposit" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentRail": "wire",
    "source": {
      "routingNumber": "021000021",
      "accountNumber": "516843515316",
      "name": "Jane Doe",
      "bankName": "JP Morgan Chase"
    },
    "amount": "100",
    "requestId": "32639f89-5fcc-4e31-8abe-0e710ba2e4a1",
    "reference": "Test deposit"
  }'
```

**Response:**

```json theme={null}
{
  "message": "Sandbox deposit triggered"
}
```

The sandbox will process this deposit as if real money arrived, triggering the full onramp flow including webhooks.

<Note>
  **Sandbox Processing Times:** Simulated deposits in sandbox are processed
  automatically after a delay: - **Wire**: \~5 minutes - **ACH**: \~8 minutes In
  production, actual processing times depend on the payment rail used (ACH: 1-3
  business days, Wire: same day, RTP: real-time).
</Note>

## Sample Code

### Get deposit instructions

<Steps>
  <Step title="Create virtual account">
    ```bash theme={null}
    curl -X POST "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts" \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "sourceCurrency": "usd",
        "destinationCurrency": "usdc",
        "destinationChain": "POLYGON"
      }'
    ```
  </Step>

  <Step title="Extract and display instructions">
    Extract the `depositInstructions` object from the response and present the relevant bank details to your user. Example:

    **ACH Deposit Instructions**

    **Bank Name:** Cross River Bank
    **Routing Number:** 021214891
    **Account Number:** 344176915009
    **Beneficiary:** John Doe

    **Wire Deposit Instructions**

    **Bank Name:** Cross River Bank
    **Routing Number:** 021214891
    **Account Number:** 344176915009
    **Beneficiary:** John Doe
    **Bank Address:** 885 Teaneck Road, Teaneck, NJ 07666
  </Step>

  <Step title="Track onramp transactions">
    List onramps to see which virtual account was used and track conversion status:

    ```bash theme={null}
    curl -X GET "https://sandbox.hifibridge.com/v2/onramps?userId=usr_abc123" \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```

    Or subscribe to `ONRAMP.CREATE` and `ONRAMP.UPDATE` webhooks for real-time notifications.
  </Step>
</Steps>

### Send to external wallet

**Option 1: Direct to external wallet**

Use an `externalWalletId` instead of `destinationChain` when creating a virtual account. Funds will be deposited directly into the specified wallet address.

```bash theme={null}
curl -X POST "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceCurrency": "usd",
    "destinationCurrency": "usdc",
    "externalWalletId": "ew_abc123"
  }'
```

Deposits to this virtual account will be converted to USDC and sent directly to the external wallet address.

**Option 2: Route through HIFI wallet**

<Steps>
  <Step title="Create virtual account">
    Create a standard virtual account without `externalWalletId`. Funds will arrive in the user's HIFI wallet first.

    ```bash theme={null}
    curl -X POST "https://sandbox.hifibridge.com/v2/users/usr_abc123/virtual-accounts" \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "sourceCurrency": "usd",
        "destinationCurrency": "usdc",
        "destinationChain": "POLYGON"
      }'
    ```
  </Step>

  <Step title="Send to external wallet">
    Send funds to any external address using the [Wallet Transfer API](/docs/transactions/transfer):

    ```bash theme={null}
    curl -X POST "https://sandbox.hifibridge.com/v2/wallets/transfers" \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "source": {
          "userId": "usr_abc123"
        },
        "destination": {
          "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
        },
        "amount": 10,
        "currency": "usdc",
        "chain": "POLYGON",
        "requestId": "b51fb3bb-8a48-5cfa-cc2g-c86g2589cdd7"
      }'
    ```
  </Step>
</Steps>

## Getting Help

* 📧 **Email:** [support@hifi.com](mailto:support@hifi.com)
* 💬 **Slack:** Message us in our shared Slack channel

## Related Resources

* [Quickstart Guide](/docs/quickstart) - Step-by-step tutorial including virtual account setup
* [Users](/docs/users) - Understand user creation and KYC requirements
* [Onramps](/docs/transactions/onramps) - Alternative onramp methods for non-USD currencies
* [Webhooks](/docs/webhooks) - Subscribe to account and onramp events
* [API Reference](https://docs.hifi.com/api-reference/virtual-account/create-a-virtual-account) - Complete endpoint documentation
