> ## 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.

# Onboard Your Customers as Merchants

> Register your own customers as VelaFi merchants through the API, send them through KYC/KYB, and activate their local currency accounts.

If your platform moves money on behalf of your own customers (individuals or companies), register each customer as a **merchant** under your VelaFi account. Each merchant passes its own KYC (individuals) or KYB (businesses) verification and gets its own **Merchant ID (MID)**. Once the merchant is approved and its currency accounts are active, you pass that MID on every payment call you make for that customer.

## How it works

```mermaid theme={null}
flowchart LR
  A["POST /v2/merchants"] --> B["Customer completes<br/>verification at kycLink"]
  B --> C["GET /v2/merchants<br/>until status = 2"]
  C --> D["POST /v2/merchant/accounts<br/>per currency"]
  D --> E["GET /v2/merchant/accounts<br/>until status = 2"]
  E --> F["Optional: virtual accounts"]
  F --> G["Use the merchantId<br/>in payment calls"]
```

1. Create the merchant. VelaFi returns a `merchantId` and a hosted `kycLink`.
2. Send your customer to the `kycLink` to complete KYC or KYB.
3. Poll the merchant's status until it is `2` (Normal).
4. Activate a currency account (channel) for each currency the customer needs.
5. Optionally create virtual accounts so each of the customer's payers gets its own deposit details.
6. Use the `merchantId` in every order, payout, transfer and query for that customer.

## Before you start

* **API access, an API key with an IP whitelist, and a token.** See [Authentication](https://docs.velafi.com/api-reference/authentication.md). All calls on this page go from your server, with the `X-BH-TOKEN` header.
* **Your own account approved.** Merchants you create sit under your VelaFi account. See [Merchants](https://docs.velafi.com/docs/merchants.md) for the multi-merchant model.
* **The customer's data ready.** Decide whether each customer is `INDIVIDUAL` or `BUSINESS`, and collect a merchant name and an email address for each.
* **A page to return users to.** You pass it as `callbackUrl`. For individual verification, VelaFi redirects the user's browser to this URL when they finish the hosted flow.

## Step by step

**1. Create the merchant**

Call [Create Merchant](https://docs.velafi.com/api-reference/merchant/create-merchant.md) (`POST /v2/merchants`) with `merchantName`, `email`, `merchantType` (`INDIVIDUAL` or `BUSINESS`) and `callbackUrl`. Send `industryType` too (and `industryTypeRemark` when it is `other`). For individuals, `flow` selects the KYC flow (`PROFESSIONALS` by default, or `CONSUMERS`), and `languageCode` sets the language of the hosted page (`en`, `es`, `pt`, `zh`).

```bash theme={null}
curl -X POST https://api.velafi.com/v2/merchants \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantName": "Example Customer 0001",
    "email": "customer0001@example.com",
    "merchantType": "BUSINESS",
    "industryType": "e-commerce",
    "callbackUrl": "https://your-app.example.com/onboarding/done",
    "languageCode": "en"
  }'
```

```json theme={null}
{
  "code": 200,
  "msg": "SUCCESS",
  "data": {
    "merchantId": 100001,
    "kycLink": "https://www.velafi.com/verify?velafi_token=abc123"
  }
}
```

Store `merchantId` against the customer in your system right away. The merchant exists from this point, even before verification.

Common rejections:

| Code | Cause |
| - | - |
| `-5005` | A merchant with this `merchantName` already exists under your account. |
| `-2017` | The `email` is already used by a VelaFi merchant. |
| `-4010` | A field is invalid, for example `merchantType`, `flow` or `email`. `msg` names the field. |
| `-5064` | Your account has reached its maximum number of merchants. Contact VelaFi. |

> [!WARNING]
>
> Creating a merchant is not idempotent. If the call times out, do not create the merchant again with different details. List your merchants filtered by the `email` you sent (see step 3) to find out whether it was created.

**2. Send the customer to the verification link**

Redirect the customer's browser to `kycLink`, or open it in an iframe in your app. Individuals complete KYC; businesses complete KYB. See [Create a Merchant](https://docs.velafi.com/docs/guides/creating-merchant.md) for both display options.

The link is time-limited. If the customer did not finish, or the link expired, get a new one by calling `POST /v2/merchants` again with the existing `merchantId`, the same `merchantType` you used at creation, and `callbackUrl`:

```json theme={null}
{
  "merchantId": 100001,
  "merchantType": "BUSINESS",
  "callbackUrl": "https://your-app.example.com/onboarding/done",
  "languageCode": "en"
}
```

This does not create a second merchant. It fails with `-5062` while the merchant's verification is under review and with `-5063` once it has been approved.

> [!NOTE]
>
> Reaching your `callbackUrl` only means the customer left the hosted flow. It does not mean they were approved. Always check the status in the next step.

**3. Check the merchant status**

Call [List Merchants](https://docs.velafi.com/api-reference/merchant/get-a-list-of-merchants.md) (`GET /v2/merchants`) and read `status` and `rejectReason`. Pass `status=-1`: without it the list returns only approved merchants. Filter with `email` or `merchantName` to find one merchant.

```bash theme={null}
curl -G https://api.velafi.com/v2/merchants \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  --data-urlencode "status=-1" \
  --data-urlencode "email=customer0001@example.com"
```

| `status` | Meaning | What to do |
| - | - | - |
| `4` | Incomplete | The customer has not finished the hosted flow. Send them a new link (step 2). |
| `1` | Authenticating | Under review. Keep polling. |
| `2` | Normal | Approved. Continue to step 4. |
| `3` | Authentication failed | Rejected. Read `rejectReason`. You can request a new link to resubmit. |

There is no webhook for merchant verification. Poll at a modest interval, for example every few minutes while the customer is onboarding and less often after that.

**4. Activate currency accounts**

For each currency the customer needs, call `POST /v2/merchant/accounts` with `merchantId`, `fiat` and `trench` (the channel name), plus the channel's `fieldList`. The request differs by merchant type:

* Individuals: [Activate Individual](https://docs.velafi.com/api-reference/merchant/activate-merchant-account-for-individual.md)
* Businesses: [Activate Business](https://docs.velafi.com/api-reference/merchant/activate-merchant-account-for-business.md)

```bash theme={null}
curl -X POST https://api.velafi.com/v2/merchant/accounts \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "100001",
    "fiat": "MXN",
    "trench": "CLABE - FINCO PAY"
  }'
```

Handle the returned `status`:

| `status` | Meaning | What to do |
| - | - | - |
| `0` with `verifyLink` | Business merchants: the channel needs an additional business verification. | Send the business to `verifyLink`. After VelaFi approves it, the channel opens without another call. |
| `1` | Pending. The provider is opening the account. | Poll [List Merchant Accounts](https://docs.velafi.com/api-reference/merchant/get-a-list-of-merchant-accounts.md). |
| `2` | Active. | Done. |
| `3` | Failed. | Read `failReason`. |
| `4` with `verifyLink` | Incomplete. For example, USD/EUR via Lead Bank needs an agreement. | Send the merchant to `verifyLink`. |

Then poll `GET /v2/merchant/accounts?merchantId=100001` until the channel shows `status: 2`. Each record also returns the channel's `paymentId`, which you need for virtual accounts, payouts and orders.

Some channels have extra requirements. BRL Pix - Woovi needs a liveness check first for individuals ([Get Woovi Liveness URL](https://docs.velafi.com/api-reference/merchant/get-woovi-liveness-url.md)) and full company and UBO data for businesses. Documents must be uploaded with [Upload File](https://docs.velafi.com/api-reference/basic-configuration/upload-file.md). See [Activate a Merchant Account](https://docs.velafi.com/docs/guides/activating-merchant-account.md) for every channel.

Common rejections: `-5073` (the merchant has not passed KYC yet), `-5074` (already active), `-5062` (activation already pending), `-5079` (no activation permission for this channel), `-4010` (a required `fieldList` field is missing or invalid).

**5. Optional: create virtual accounts**

If the customer collects from many payers, give each payer its own deposit details with [Create Virtual Accounts](https://docs.velafi.com/api-reference/merchant/create-multiple-virtual-account.md) (`POST /v2/merchant/virtual/accounts`). Send `merchantId`, `fiat`, the channel's `paymentId` and an `alias` of letters and digits. Virtual accounts are available for MXN, ARS, BRL and COP, and the currency account must already be active.

Read the deposit details with [List Virtual Accounts](https://docs.velafi.com/api-reference/merchant/get-multiple-virtual-account.md). Deposits to a virtual account carry its alias as `depositAlias`. See [Collect local payments with virtual accounts](https://docs.velafi.com/docs/use-cases/collect-with-virtual-accounts.md) for the full collection flow.

## Use the merchant ID in every other call

The `merchantId` decides whose balance, limits and accounts a call uses. Send the customer's MID, not your own, on every call you make for that customer:

| Call | Field |
| - | - |
| On-ramp and off-ramp orders (`POST /v2/order/fiat_to_crypto`, `POST /v2/order/crypto_to_fiat`) | `merchantId` |
| Global payment orders (`POST /v2/order/fiat_to_fiat`) | `onRampMerchantId`, `offRampMerchantId` |
| Payouts from the fiat balance (`POST /v2/merchant/claimfunds`) | `merchantId` |
| Transfers between merchants (`POST /v2/merchant/transfer`) | `fromMerchantId`, `toMerchantId` |
| Beneficiaries (`POST /v2/payments`) | `merchantId` of the merchant whose currency account the beneficiary belongs to. Always pass it: without it, adding a beneficiary for an automatic payment method fails. |
| Crypto withdrawals and deposit addresses | `merchantId` |
| Deposit details, funding records, fiat flows, balances | `merchantId` filter |

Keep a one-to-one mapping between your customer ID and the VelaFi MID, and never let a customer-supplied value choose the MID.

## Webhooks to handle

Merchant verification and channel activation have no webhook. Poll `GET /v2/merchants` and `GET /v2/merchant/accounts` as described above.

Webhooks are registered per VelaFi account, not per merchant: one registration per event type receives events for all of your merchants. Order, funding, crypto withdrawal and stablecoin payment events carry a `merchantId`, so route them by it. For events without one, look up the record by its ID. See [Webhook Setup](https://docs.velafi.com/api-reference/webhooks/webhook-setup.md).

## Handle failures

* **Verification rejected (`status: 3`).** Show `rejectReason` to your customer, then request a new link with the same `merchantId` and `merchantType`.
* **Customer abandons the flow (`status: 4`).** Send a reminder with a fresh link. Do not create a new merchant; the name and email are already taken by the first one.
* **Activation failed (`status: 3`).** Read `failReason` in the activation response or in List Merchant Accounts. Fix the `fieldList` data or contact VelaFi.
* **Activation pending for a long time.** Pending channels depend on the provider. Keep polling and contact VelaFi if the channel does not become active.
* **Timeouts and outcome-unknown errors** (`-1001`, `-1006`, `-1007`). The merchant or channel may have been created. Check with List Merchants or List Merchant Accounts before you call again. See [Error Codes](https://docs.velafi.com/docs/error-codes.md#outcome-unknown-check-before-you-retry).

## Reconcile

* `GET /v2/merchants` with `status=-1` is the source of truth for verification status and for each merchant's daily and monthly limit usage.
* `GET /v2/merchant/accounts` is the source of truth for which channels are active, their `paymentId`, balance, and monthly buy/sell limits.

Run a periodic job that compares your customer records with both lists, so a customer never stays stuck in a state your system does not know about.

## Test in the sandbox

Your sandbox account comes with pre-configured test merchants, and no real KYC/KYB takes place in the sandbox. Use those merchants to test activation results, virtual accounts and the payment flows that follow. Write your status handling against every status code in the tables above, because you cannot make a real reviewer approve or reject a sandbox merchant. See [Sandbox](https://docs.velafi.com/docs/sandbox.md).

## Go-live checklist

* Store `merchantId` as soon as `POST /v2/merchants` returns, and map it one-to-one to your customer.
* Handle every merchant status (`1`, `2`, `3`, `4`) and every activation status (`0` to `4`), including `verifyLink` redirects.
* Poll with `status=-1`, so merchants that are not approved yet are not missing from your results.
* Re-request expired links with the existing `merchantId` and `merchantType` instead of creating a duplicate merchant.
* Wait for the channel to show `status: 2` before you create virtual accounts or send payments for that currency.
* Pass the customer's MID on every downstream call, and route webhook events by `merchantId`.

Then work through the shared [go-live checklist](https://docs.velafi.com/docs/use-cases/go-live-checklist.md).


## Related topics

- [Core Use Cases](https://docs.velafi.com/docs/use-cases/overview.md)
- [Agent Skills](https://docs.velafi.com/docs/developer-tools/agent-skills.md)
- [Merchants](https://docs.velafi.com/docs/merchants.md)
- [Create a Merchant](https://docs.velafi.com/docs/guides/creating-merchant.md)
- [Activate a Business Merchant Account](https://docs.velafi.com/api-reference/merchant/activate-merchant-account-for-business.md)


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