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

# On-ramp: Fund a stablecoin account with a USD wire

> Create a Virtual Account, share wire instructions with the customer, and let Bastion auto-convert the USD deposit into USDC when the wire lands.

## Goal

Fund a customer's Bastion account with USDC by converting USD wired from their bank account, using an on-ramp conversion via a Virtual Account (VA).

> **Scope:** USD → USDC, Wire only, US customers only, first-party (customer wires from their own bank account).

## Key entities

| Entity                   | Description                                                                                                      |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| **Your platform**        | The product integrating Bastion's APIs                                                                           |
| **Customer**             | An individual or business your platform has onboarded (KYC/KYB-verified), with a Bastion account                 |
| **Identity**             | The customer's record in Bastion                                                                                 |
| **Account**              | A Bastion account linked to the identity that holds balances and transacts                                       |
| **Virtual Account (VA)** | A dedicated deposit address at Bastion's partner bank tied to a specific customer, chain, and destination wallet |
| **Conversion**           | A value change between assets (e.g., USD wired in → USDC on Ethereum) auto-created when a deposit lands          |

***

## Flow overview

The steps to fund a stablecoin balance from a bank account are:

1. Create a Virtual Account
2. Share deposit instructions with the customer
3. Customer wires USD
4. Track conversion status
5. Show balance

Unlike off-ramps, the partner does not call [`POST /v2/conversions`](/v2/api-reference/conversions/submit-conversion). The conversion is auto-created the moment Bastion detects the incoming wire.

***

## Prerequisites

* Customer has a **verified identity** (KYC or KYB) with `fiat_operations_enabled = true`
* Customer has an active Bastion account on a supported chain
* You have an API key and registered webhook

***

## Step 1 – create a virtual account

Create a Virtual Account for the customer. Each VA links to a specific chain, asset, and destination wallet; create one VA per chain and asset you want to support.

**Request**

<CodeGroup>
  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  POST /v2/identities/{identity_id}/virtual-accounts
  Content-Type: application/json
  Authorization: Bearer YOUR_API_KEY
  ```
</CodeGroup>

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "request_id": "903c9e51-51af-4597-9174-0a93ca80dbac",
    "source": {
      "banking_country": "US",
      "currency": "USD"
    },
    "destination": {
      "currency": "USDC",
      "payment_method": "BLOCKCHAIN",
      "chain": "ETHEREUM_MAINNET",
      "account_id": "36hv3yZpsHt7pwgBZom5SXgURYz"
    }
  }
  ```
</CodeGroup>

**Field notes**

| Field                    | Description                                                    |
| ------------------------ | -------------------------------------------------------------- |
| `source.banking_country` | Determines the banking partner. `US` for US customers          |
| `source.currency`        | Fiat currency the VA accepts. `USD` for US Wire                |
| `destination.chain`      | Blockchain network for USDC delivery (e.g. `ETHEREUM_MAINNET`) |
| `destination.account_id` | Customer's Bastion account that receives USDC                  |

**Response**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "id": "va_3Cb0rR32xNl2GAHFjJc4EHEcdPw",
    "identity_id": "36RNhc8l8oQLLUS2aJaXZaUbwnC",
    "status": "ACTIVE",
    "source": {
      "banking_country": "US",
      "currency": "USD"
    },
    "destination": {
      "currency": "USDC",
      "payment_method": "BLOCKCHAIN",
      "chain": "ETHEREUM_MAINNET",
      "account_id": "36hv3yZpsHt7pwgBZom5SXgURYz"
    },
    "deposit_instructions": {
      "bank_name": "Partner Bank",
      "bank_address": "123 Banking St, New York, NY 10001",
      "bank_account_number": "9876543210",
      "bank_routing_number": "021000000",
      "bank_beneficiary_name": "John Doe",
      "bank_beneficiary_address": "99 Main St, New York, NY 10001",
      "payment_method": "WIRE",
      "currency": "USD"
    },
    "created_at": "2026-04-19T15:00:00Z",
    "updated_at": "2026-04-19T15:00:00Z"
  }
  ```
</CodeGroup>

Store the `id` as `virtual_account_id` so you can reference this VA later. You can retrieve it again at any time from the [`GET /v2/identities/{identity_id}/virtual-accounts`](/v2/api-reference/virtual-accounts/list-virtual-accounts) list endpoint.

***

## Step 2 – share deposit instructions with the customer

Present the `deposit_instructions` from Step 1 to the customer so they can initiate a wire from their bank.

**Fields to surface in your UI**

| Field                      | Label for the customer |
| -------------------------- | ---------------------- |
| `bank_name`                | Bank name              |
| `bank_address`             | Bank address           |
| `bank_account_number`      | Account number         |
| `bank_routing_number`      | Routing number (ABA)   |
| `bank_beneficiary_name`    | Beneficiary name       |
| `bank_beneficiary_address` | Beneficiary address    |

The `bank_account_number` is unique to this VA. The customer does not need to include a memo or reference — the account number alone routes the deposit to the correct VA.

***

## Step 3 – customer wires USD

The customer initiates a domestic wire from their bank to the deposit instructions shared in Step 2. When the wire lands at Bastion's banking partner, Bastion detects it and auto-creates a conversion record.

No API call is required from your platform at this step.

***

## Step 4 – track conversion status

Once the wire is detected, Bastion creates the conversion and fires webhooks as it progresses. You learn the `conversion_id` from the first webhook.

### Option A: webhooks

**Example event (INITIATED)**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "id": "cv_334455",
    "request_id": "auto-generated-uuid",
    "amount": "500.00",
    "account_id": "36hv3yZpsHt7pwgBZom5SXgURYz",
    "payment_purpose": "PERSONAL_REMITTANCE",
    "source": {
      "currency": "USD",
      "payment_method": "WIRE",
      "amount": "500.00"
    },
    "destination": {
      "currency": "USDC",
      "payment_method": "BLOCKCHAIN",
      "chain": "ETHEREUM_MAINNET",
      "amount": ""
    },
    "type": "ON_RAMP",
    "status": "INITIATED",
    "created_at": "2026-04-19T15:00:00Z",
    "updated_at": "2026-04-19T15:00:00Z",
    "completed_at": "",
    "exchange_rate": "1.0",
    "fees_total": "14.00",
    "fees_total_currency": "USDC",
    "fees": [
      {
        "amount": "14.00",
        "currency": "USDC",
        "type": "PLATFORM"
      }
    ]
  }
  ```
</CodeGroup>

**Example event (COMPLETED)**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "id": "cv_334455",
    "request_id": "auto-generated-uuid",
    "amount": "500.00",
    "account_id": "36hv3yZpsHt7pwgBZom5SXgURYz",
    "payment_purpose": "PERSONAL_REMITTANCE",
    "source": {
      "currency": "USD",
      "payment_method": "WIRE",
      "amount": "500.00"
    },
    "destination": {
      "currency": "USDC",
      "payment_method": "BLOCKCHAIN",
      "chain": "ETHEREUM_MAINNET",
      "amount": "486.00"
    },
    "type": "ON_RAMP",
    "status": "COMPLETED",
    "created_at": "2026-04-19T15:00:00Z",
    "updated_at": "2026-04-19T15:45:00Z",
    "completed_at": "2026-04-19T15:45:00Z",
    "exchange_rate": "1.0",
    "fees_total": "14.00",
    "fees_total_currency": "USDC",
    "fees": [
      {
        "amount": "14.00",
        "currency": "USDC",
        "type": "PLATFORM"
      }
    ]
  }
  ```
</CodeGroup>

**Status progression**

The on-ramp progression is:

`INITIATED` → `PROCESSING` → `PAYOUT_INITIATED` → `PAYOUT_IN_PROGRESS` → `COMPLETED`

**Handle statuses:**

| Status                                   | Action                                                               |
| ---------------------------------------- | -------------------------------------------------------------------- |
| `INITIATED`, `PROCESSING`                | Show "funding your account" indicator.                               |
| `PAYOUT_INITIATED`, `PAYOUT_IN_PROGRESS` | Show "delivering USDC"; USDC is on the way to the customer's wallet. |
| `COMPLETED`                              | Mark on-ramp complete, refresh balances, show as settled.            |
| `FAILED`                                 | Show error, direct to support.                                       |

> **Webhook reliability:** Failed deliveries are retried with exponential backoff (15 s base interval, 10 min max). Deduplicate using `id` + `status`. If no webhook arrives within a reasonable window, poll [`GET /v2/conversions/{conversion_id}`](/v2/api-reference/conversions/get-conversion).

### Option B: polling

If webhook delivery fails, list conversions by account to find the record:

<CodeGroup>
  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  GET /v2/conversions?account_id={account_id}&type=ON_RAMP
  Authorization: Bearer YOUR_API_KEY
  ```
</CodeGroup>

Then poll [`GET /v2/conversions/{conversion_id}`](/v2/api-reference/conversions/get-conversion) for status updates.

***

## Step 5 – show updated balance

After the conversion completes, refresh balances and show the on-ramp in the customer's history.

**Refresh balances**

<CodeGroup>
  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  GET /v2/accounts/{account_id}/balances
  Authorization: Bearer YOUR_API_KEY
  ```
</CodeGroup>

**UI recommendations:**

* Increase visible USDC balance by `destination.amount` from the completion webhook
* Display USD wired (gross), fees, USDC delivered (net), and status
* Show source (masked bank account details from the wire)

***

## Fees

`fees_total` on the conversion reflects the incoming wire fee only. The fee is deducted from the wired USD amount before USDC is delivered.

Example: customer wires \$500 → `fees_total = 14.00` → `destination.amount = 486.00` USDC delivered.

The `fees` array is populated from the moment the conversion is created (when the wire lands).

Developer fees are not supported yet.

***

## Implementation notes

| Recommendation                       | Details                                                                                                                                          |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **One VA per chain**                 | Create a separate VA for each chain (Ethereum, Solana, Base) you want to support                                                                 |
| **VAs are reusable**                 | The same VA accepts multiple deposits. Don't create a new VA per transaction.                                                                    |
| **Learn conversion ID from webhook** | The partner doesn't create the conversion; the `id` arrives in the first webhook                                                                 |
| **Map IDs to your users**            | Store `identity_id`, `account_id`, and `virtual_account_id` alongside your internal user records so webhooks can be routed to the right customer |
| **Handle failures clearly**          | Treat `FAILED` as errors requiring customer messaging                                                                                            |


## Related topics

- [Stablecoin on-ramp and off-ramp conversions](/guides/concepts/stablecoin-conversions.md)
- [Jurisdiction overview](/guides/getting-started/jurisdiction-overview.md)
- [Onboard a business customer](/guides/quickstarts/onboard-a-business-customer.md)
- [Off-ramp: Withdraw stablecoin to a US bank account](/guides/quickstarts/off-ramp/withdraw-to-a-bank-account.md)
- [Conversions API overview](/v2/api-reference/conversions/overview.md)
