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

# Collect Local Payments with Virtual Accounts

> Give each payer their own local bank details, credit their payments to the merchant's fiat balance, and match every deposit to the payer with depositAlias.

Use this flow to collect local-currency payments (MXN, ARS, COP, BRL) from your own customers into a merchant's fiat balance, also called the Pending Fund balance. You give each payer a virtual account with its own bank details, the payer sends a local transfer, and VelaFi credits the merchant's balance and sends you a `FUNDING_WEBHOOK` that names the virtual account the money arrived on. From that balance you can [pay out](https://docs.velafi.com/docs/use-cases/payouts-from-balance.md), move funds to another merchant, or buy crypto.

## How it works

```mermaid theme={null}
sequenceDiagram
    participant You as Your platform
    participant V as VelaFi
    participant P as Payer
    You->>V: POST /v2/merchant/virtual/accounts (alias = payer ID)
    You->>V: GET /v2/merchant/virtual/addfunds (depositAlias)
    V-->>You: Bank details for that alias (fieldList)
    You->>P: Show the bank details
    P->>V: Local transfer (SPEI, CVU, Pix, Bre-B...)
    V->>V: Credit the merchant's fiat balance
    V->>You: FUNDING_WEBHOOK (type DEPOSIT, status 2, depositAlias)
    You-->>V: HTTP 200 {"callbackStatus":"SUCCESS"}
    You->>V: GET /v2/merchant/funding/records (reconcile)
```

### One account or one per payer

| Option | How you get the details | How you know who paid | Use it when |
| - | - | - | - |
| Merchant currency account | [`GET /v2/merchant/addfunds`](https://docs.velafi.com/api-reference/merchant/get-pending-fund-account) | Payer fields in the webhook (`payerName`, `payerAccount`, `taxId`), which depend on the rail | You fund your own balance, or you have few payers |
| One virtual account per payer | [`POST /v2/merchant/virtual/accounts`](https://docs.velafi.com/api-reference/merchant/create-multiple-virtual-account), then [`GET /v2/merchant/virtual/addfunds`](https://docs.velafi.com/api-reference/merchant/get-multiple-virtual-account) | `depositAlias` in the webhook and in the funding record | You collect from many customers and need to attribute every payment |

> [!NOTE]
>
> A virtual account is for attribution only. It has no balance of its own: every deposit, on any virtual account, is credited to the single balance of the merchant's currency account. Keep per-payer balances in your own ledger, keyed by `depositAlias`.

## Before you start

* **API access.** An API key, your server IP on the whitelist, and a business token in the `X-BH-TOKEN` header. See [Authentication](https://docs.velafi.com/api-reference/authentication.md).
* **A verified merchant.** See [Create a Merchant](https://docs.velafi.com/docs/guides/creating-merchant.md).
* **An active currency account** for the currency and channel you want to collect on (status `2` in [List Merchant Accounts](https://docs.velafi.com/api-reference/merchant/get-a-list-of-merchant-accounts.md)). See [Activate a Merchant Account](https://docs.velafi.com/docs/guides/activating-merchant-account.md).
* **A `FUNDING_WEBHOOK` endpoint** registered and returning the required acknowledgement. See [Webhooks](https://docs.velafi.com/docs/guides/webhooks.md) and [Webhook Setup](https://docs.velafi.com/api-reference/webhooks/webhook-setup.md).

### Channels that support virtual accounts

| Currency | `paymentId` | Channel | Alias rule |
| - | - | - | - |
| MXN | `105` | SPEI (Finco Pay) | Letters and digits, up to 64 characters |
| MXN | `72` | SPEI (Tesored) | Letters and digits, up to 64 characters |
| ARS | `63` | CBU/CVU (Momentum) | Letters and digits, up to 64 characters |
| COP | `68` | Automatic ACH (payment link) | 6 to 21 uppercase letters or digits |
| COP | `69` | Bre-B | 6 to 21 uppercase letters or digits |
| BRL | `90` | Pix (Genial) | Letters and digits, up to 64 characters |
| BRL | `92` | Pix - Woovi (Avenia) | Letters and digits, up to 64 characters |

The merchant must already have an active currency account on that channel. A currency account on a channel that does not support virtual accounts returns `-5084` (this currency does not support multiple virtual accounts). `default`, in any letter case, is reserved.

## Step by step

**1. Create a virtual account for each payer**

Call [`POST /v2/merchant/virtual/accounts`](https://docs.velafi.com/api-reference/merchant/create-multiple-virtual-account) with the merchant, currency, channel and an `alias` you choose. Use a stable ID from your system, such as your customer ID, so you can match deposits without a lookup table. The alias is permanent.

```bash cURL theme={null}
curl -X POST https://api.velafi.com/v2/merchant/virtual/accounts \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": 100001,
    "fiat": "MXN",
    "paymentId": 105,
    "alias": "CUST0001"
  }'
```

```json Response theme={null}
{ "code": 200, "msg": "SUCCESS", "data": { "fiat": "MXN", "status": 1 } }
```

`status` is `1` (pending) while the channel sets up the account, `2` when it is active, and `3` if it failed. ARS (`63`) accounts return `2` at once. Sending the same alias again while it is pending returns `-5085`; once it is active, `-5086`.

**2. Get the bank details for the alias**

Poll [`GET /v2/merchant/virtual/addfunds`](https://docs.velafi.com/api-reference/merchant/get-multiple-virtual-account) with `depositAlias` until the record's `status` is `2`, then read its `fieldList`. The keys depend on the channel and are listed in [`fieldList` keys by payment method](https://docs.velafi.com/api-reference/merchant/get-pending-fund-account#fieldlist-keys-by-payment-method).

```bash cURL theme={null}
curl -G https://api.velafi.com/v2/merchant/virtual/addfunds \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  --data-urlencode "merchantId=100001" \
  --data-urlencode "fiat=MXN" \
  --data-urlencode "paymentId=105" \
  --data-urlencode "depositAlias=CUST0001"
```

Store the details against the payer. QR image links in `fieldList` expire one hour after the response, so fetch a fresh one when you show it again. To fund the merchant's main account instead, call [`GET /v2/merchant/addfunds`](https://docs.velafi.com/api-reference/merchant/get-pending-fund-account).

**3. Show the payer what to do**

Show the payer the details for their alias. What the payer does depends on the rail:

| Rail | What the payer does |
| - | - |
| MXN, SPEI (Finco Pay `105`, Tesored `72`) | Sends a SPEI transfer to the CLABE (`Cuenta CLABE` or `clabeNumber`). |
| ARS, CBU/CVU (Momentum `63`) | Sends a bank transfer to the `CVU Number` (or its `Alias`, when present). |
| COP, Automatic ACH (`68`) | Opens the `paymentLink` and pays. |
| COP, Bre-B (`69`) | Sends a Bre-B transfer to the `brebKey`. |
| BRL, Pix (Genial `90`) | Pays to the Pix key (`Chave Pix - Random Pix`), the copy-and-paste code, or the QR code. |
| BRL, Pix - Woovi (Avenia `92`) | Pays the static Pix QR (`qrCodeLink`, `qrCode`) or the `pixKey`, when present. |

**4. Handle the deposit webhook**

When the deposit is credited, VelaFi creates a funding record of type `DEPOSIT` with status `2` and sends a `FUNDING_WEBHOOK`. Verify the `signature` header, store the event, and return HTTP `200` with `{"callbackStatus":"SUCCESS"}`. Credit the payer identified by `depositAlias` in your ledger. See [Webhooks to handle](#webhooks-to-handle).

**5. Check the balance**

Read the merchant's balance from [`GET /v2/merchant/accounts`](https://docs.velafi.com/api-reference/merchant/get-a-list-of-merchant-accounts): the `balance` of the record for that currency and channel. This is the amount available for payouts, transfers and on-ramp orders.

```bash cURL theme={null}
curl -G https://api.velafi.com/v2/merchant/accounts \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  --data-urlencode "merchantId=100001" \
  --data-urlencode "fiat=MXN"
```

## Webhooks to handle

Subscribe to `FUNDING_WEBHOOK`. One webhook receives events for every merchant under your account. A deposit event looks like this (all values are strings):

```json FUNDING_WEBHOOK (DEPOSIT) theme={null}
{
  "webhookId": "ebab8a9663704e7cbbebe672c7c67912",
  "eventType": "FUNDING_WEBHOOK",
  "txId": "1123123123",
  "merchantId": "100001",
  "fiat": "MXN",
  "totalAmount": "1000",
  "amount": "1000",
  "fee": "0",
  "paymentId": "105",
  "type": "DEPOSIT",
  "status": "2",
  "depositAlias": "CUST0001",
  "payerName": "Example Payer",
  "payerAccount": "000000000000000002",
  "bankWebhookInfo": "{...}",
  "completedTime": "1737452344000",
  "createTime": "1737452292000",
  "updateTime": "1737452344000",
  "timestamp": "1737452344512"
}
```

| Field | Use it to |
| - | - |
| `txId` | Identify the funding record. Use it as your idempotency key, together with `eventType` and `status`. |
| `type` | Route the event. Deposits are `DEPOSIT`. The same webhook also carries `WITHDRAW` and `BUY_CRYPTO`. Transfers between merchants are not sent. |
| `status` | `2` completed, `3` canceled, `4` refunded. Pending records are not sent. |
| `depositAlias` | Attribute the deposit to the payer's virtual account. |
| `amount`, `fee`, `totalAmount` | Book the net amount and the fee. |
| `payerName`, `payerAccount`, `taxId`, `cardNo` | Record who sent the money. Sent only when the rail provides them, so treat them as optional. |
| `bankWebhookInfo` | Provider-specific details as a JSON string, for example the SPEI `tracking_id` or the ARS `idCoelsa`. See [Bank Webhook Info Details](https://docs.velafi.com/api-reference/webhooks/funding-records-message-push.md#bank-webhook-info-details). |

The full field list is in [Funding Event](https://docs.velafi.com/api-reference/webhooks/funding-records-message-push.md).

> [!WARNING]
>
> A delivery counts as successful only when your endpoint returns a `2xx` status **and** the JSON body `{"callbackStatus":"SUCCESS"}`. Respond quickly: VelaFi waits at most 60 seconds, and a timeout counts as a failed delivery. A plain `SUCCESS`, `{"received":true}`, an empty body or your own error JSON all count as failures. After three failed attempts the event is not sent again, and the Open API has no redelivery endpoint. Acknowledge first, then process asynchronously. See [Delivery and Retries](https://docs.velafi.com/api-reference/webhooks/webhook-setup.md#delivery-and-retries).

## Handle failures

* **Duplicate or out-of-order events.** The same event can arrive more than once, and an event whose status has changed before delivery is dropped. Make the handler idempotent on `eventType` + `txId` + `status`, and read the record from [List Funding Records](https://docs.velafi.com/api-reference/merchant/retrieve-funding-records.md) when you need its latest state.
* **Missed webhooks.** If your endpoint was down or answered incorrectly, nothing is resent. Run the reconciliation below on a schedule so no deposit is missed.
* **Deposit without a `depositAlias`.** Payments to the merchant's main account details, or rails that do not report the alias, have no virtual account to match. Hold them for manual review using the payer fields and `bankWebhookInfo`.
* **Virtual account creation fails.** `status` `3` means the channel rejected the account. `-5079` means the account is not enabled for that currency and channel, and `-5071` means the merchant's currency account on that channel is not active. Check the merchant account before retrying.
* **COP alias rejected.** For `68` and `69`, the alias must be 6 to 21 uppercase letters or digits.

## Reconcile

The funding records are the source of truth for what was credited to the balance:

* [`GET /v2/merchant/funding/records`](https://docs.velafi.com/api-reference/merchant/retrieve-funding-records) with `type=DEPOSIT`, a `fiat`, and a `startTime`/`endTime` window (the window applies to the completion time). Compare every record with status `2` against the webhooks you stored, and process any you missed. For deposits, `webhookInfo` holds the same provider details as `bankWebhookInfo`, including `depositAlias`.
* [`GET /v2/merchant/fiat/flows`](https://docs.velafi.com/api-reference/merchant/get-a-list-merchant-fiat-flow) for the line-by-line balance history: each line has the change (`changed`), the running balance (`total`) and the related `txId`. Use it to prove that the balance in [List Merchant Accounts](https://docs.velafi.com/api-reference/merchant/get-a-list-of-merchant-accounts.md) equals the sum of your movements.

Funding records return 100 records per page by default (up to 20,000 with `pageSize`), and fiat flows up to 1,000 per page. Page through with `currentPage`.

## Test in the sandbox

Use `https://api-test.velafi.com` with sandbox credentials. You can create and list virtual accounts there, and credit test deposits with the [fiat deposit simulator](https://docs.velafi.com/docs/sandbox.md#sandbox-testing-tools) (`POST /v2/platform/test/deposit`). Pass `depositAlias` to send the deposit to a virtual account.

* **Webhooks do not fire in the sandbox.** Test your handler by posting sample payloads to it yourself, and check the result of each simulated deposit with [List Funding Records](https://docs.velafi.com/api-reference/merchant/retrieve-funding-records.md).
* The simulator only produces successful deposits. It cannot produce a failed or returned deposit.

## Go-live checklist

* [ ] The `FUNDING_WEBHOOK` endpoint verifies the signature and returns HTTP `200` with `{"callbackStatus":"SUCCESS"}` quickly, well within the 60-second timeout.
* [ ] The handler is idempotent on `eventType` + `txId` + `status` and ignores events it has already processed.
* [ ] Every payer has their own virtual account, and the alias maps to one customer in your system.
* [ ] Your ledger keeps per-payer balances; you do not expect VelaFi to hold a balance per virtual account.
* [ ] Deposits without a `depositAlias` go to a review queue.
* [ ] A scheduled job compares funding records (`type=DEPOSIT`) with stored webhooks and processes the gaps.
* [ ] QR codes and payment links are fetched fresh before they are shown.

See also the general [go-live checklist](https://docs.velafi.com/docs/use-cases/go-live-checklist.md).


## Related topics

- [Pay Out Local Currency from the Balance](https://docs.velafi.com/docs/use-cases/payouts-from-balance.md)
- [Convert Local Currency to Stablecoins](https://docs.velafi.com/docs/use-cases/convert-to-stablecoins.md)
- [Agent Skills](https://docs.velafi.com/docs/developer-tools/agent-skills.md)
- [Core Use Cases](https://docs.velafi.com/docs/use-cases/overview.md)
- [Onboard Your Customers as Merchants](https://docs.velafi.com/docs/use-cases/onboard-your-customers.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.