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

# Virtual accounts

> Give your client a persistent bank account that auto-converts every deposit to crypto in their Portal wallet.

## Overview

A virtual account is a persistent fiat bank account tied to your client's Portal wallet. Every
deposit auto-converts to crypto and settles onchain, with no per-deposit API call. Use it as a
standing on-ramp, in contrast to a one-off [payin](/integrations/On-Off-Ramp/due-payins) that
quotes and settles a single transfer.

<Note>
  Portal proxies these endpoints to Due and returns Due's responses verbatim. Example
  responses below are illustrative; exact fields come from Due and may evolve.
</Note>

## 1. Prerequisite: approved KYC

Virtual accounts require a provisioned Due customer with approved KYC. Some channels
additionally require endorsements. See [KYC onboarding](/integrations/On-Off-Ramp/due-kyc).

## 2. Discover a channel

`GET /channels` lists the rails and currencies available to the customer. Pass
`onlyAvailable=true` to filter to channels the customer can use today. Pick the fiat deposit
channel (the source, for example `sepa` / `EUR`) and the crypto destination rail and currency
(for example `base-sepolia` / `USDC`).

```bash theme={null}
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/due/channels?onlyAvailable=true' \
  --header 'Authorization: Bearer [token]'
```

## 3. Register the Portal wallet

The virtual account settles crypto to a wallet Due knows about, so register your client's
Portal wallet first. Call `POST /wallets` with the wallet address.

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/wallets \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "address": "0xc118ad00663be7d0360b56a4e9fbb20530bf4693"
  }'
```

Example response:

```json theme={null}
{
  "data": {
    "id": "wlt_123",
    "address": "evm:0xc118ad00663be7d0360b56a4e9fbb20530bf4693",
    "preferences": {
      "selectedNetworks": ["base-sepolia"]
    }
  }
}
```

<Note>
  The wallet is usable as a destination only once Due binds it to networks, shown as a
  non-empty `preferences.selectedNetworks`. Use `GET /wallets` to list registered wallets and
  confirm. The returned `id` (`wlt_...`) is the virtual account `destination` in the next step.
</Note>

## 4. Create the virtual account

Call `POST /virtual-accounts`. The fiat side is the deposit channel (`schemaIn`, `currencyIn`)
and the crypto side is where funds settle (`railOut`, `currencyOut`).

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/virtual-accounts \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "destination": "wlt_123",
    "schemaIn": "bank_sepa",
    "currencyIn": "EUR",
    "railOut": "base-sepolia",
    "currencyOut": "USDC",
    "reference": "customer-1234"
  }'
```

* `destination`: the `wlt_...` id from step 3. Converted crypto settles here.
* `schemaIn`: the fiat deposit bank schema (for example `bank_sepa` or `bank_us`).
* `currencyIn`: the fiat currency accepted for deposits (for example `EUR`). Optional; if
  omitted, Due resolves a default and the returned `key` includes the resolved currency, so
  always use the returned `key` for later reads and updates.
* `railOut` / `currencyOut`: the crypto rail and asset funds convert to.
* `reference`: your own label for the account, also used to build its key.

Example response:

```json theme={null}
{
  "data": {
    "id": "va_789",
    "key": "wlt_123/bank_sepa/EUR/base-sepolia/USDC/customer-1234",
    "isActive": false,
    "details": null
  }
}
```

## 5. Wait for the account to activate

Due provisions the underlying bank account asynchronously (up to about a minute). Subscribe to
[webhooks](/integrations/On-Off-Ramp/due-webhooks) to learn when it activates. You can also
read the account by its composite key at any time; it is ready once `isActive` is `true` and
`details` is populated. The key is `destination/schemaIn/currencyIn/railOut/currencyOut/reference`.

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/virtual-accounts/key/wlt_123/bank_sepa/EUR/base-sepolia/USDC/customer-1234 \
  --header 'Authorization: Bearer [token]'
```

Example response once active:

```json theme={null}
{
  "data": {
    "id": "va_789",
    "isActive": true,
    "details": {
      "bankName": "Lead Bank",
      "beneficiary": "Due Payments",
      "iban": "DE89370400440532013000",
      "bic": "DEUTDEFF",
      "reference": "customer-1234"
    }
  }
}
```

<Tip>
  Webhooks also notify you when deposits arrive and convert, so you never have to check the
  account yourself. Acknowledge deliveries quickly with a `2XX`, then process events asynchronously.
</Tip>

## 6. Fund the account

Show the `details` bank information to your client. Any deposit to that account auto-converts
to `currencyOut` and settles in their Portal wallet. There is no per-deposit API call.

## Managing the account

* `GET /virtual-accounts/list` lists the customer's virtual accounts.
* `POST /virtual-accounts/key/{key}` updates an account by its composite key.

<Note>
  Application fees are enforced by the custodian's Portal Dashboard configuration and injected
  server-side. Any `applicationFeeBps` or `applicationFeeAmount` you send is stripped, so
  clients cannot override them. See [Application fees](/integrations/On-Off-Ramp/due-fees).
</Note>

## Next steps

* [Payins](/integrations/On-Off-Ramp/due-payins)
* [Payouts](/integrations/On-Off-Ramp/due-payouts)
* [Webhooks](/integrations/On-Off-Ramp/due-webhooks)
