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

# Pay Out Local Currency from the Balance

> Send local-currency payouts from a merchant's fiat balance to a saved beneficiary, track them with FUNDING_WEBHOOK, and move balance between your merchants.

Use this flow to pay out local currency (MXN, ARS, COP, BRL, PEN) from a merchant's fiat balance, also called the Pending Fund balance, to a bank account or Pix key you have saved as a beneficiary. You call `POST /v2/merchant/claimfunds`, VelaFi debits the balance and sends the payout on the local rail, and a `FUNDING_WEBHOOK` tells you whether it completed. The same balance can also be moved to another of your merchants with `POST /v2/merchant/transfer`.

The balance usually comes from deposits. See [Collect Local Payments with Virtual Accounts](https://docs.velafi.com/docs/use-cases/collect-with-virtual-accounts.md).

## How it works

```mermaid theme={null}
sequenceDiagram
    participant You as Your platform
    participant V as VelaFi
    participant B as Beneficiary bank
    You->>V: POST /v2/payments (save beneficiary)
    V-->>You: id (userPaymentId)
    You->>V: POST /v2/merchant/claimfunds (clientId, amount, userPaymentId)
    V->>V: Debit amount + fee from the balance
    V-->>You: txId, fee, totalAmount
    V->>B: Local payout (SPEI, CVU, Pix, Bre-B...)
    V->>You: FUNDING_WEBHOOK (type WITHDRAW, status 2 or 3)
    You-->>V: HTTP 200 {"callbackStatus":"SUCCESS"}
```

## 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 with an active currency account** on a channel that supports payouts (see below). See [Activate a Merchant Account](https://docs.velafi.com/docs/guides/activating-merchant-account.md).
* **Balance on that currency account**, from deposits on the same channel. See [Collect Local Payments with Virtual Accounts](https://docs.velafi.com/docs/use-cases/collect-with-virtual-accounts.md).
* **A `FUNDING_WEBHOOK` endpoint** registered and returning the required acknowledgement. See [Webhook Setup](https://docs.velafi.com/api-reference/webhooks/webhook-setup.md).

### Channels that support payouts from the balance

| Currency | Channel | Beneficiary `paymentId` |
| - | - | - |
| MXN | SPEI (Finco Pay) | `105` |
| MXN | SPEI (Tesored) | `72` |
| ARS | CBU/CVU (Momentum) | `63` |
| COP | COP Account (Cobre) | `68`, `69` |
| BRL | Pix (Genial) | `90` |
| BRL | Pix - Woovi (Avenia) | `92` |
| PEN | Bank Transfer | `95` (minimum payout 10 PEN) |

Three rules apply to every payout:

1. **Same channel.** The payout goes out through the channel of the currency account that holds the balance. Only balance that came in through that channel (or through VelaFi Pending Fund) can be paid out on it.
2. **Same merchant currency account.** The beneficiary must be saved under the same merchant, for the same currency and channel as that account. A beneficiary saved under another merchant, or for another channel (for example a Tesored beneficiary against a Finco Pay balance), is rejected.
3. **Amount plus fee.** The balance must cover `amount` plus the payout fee.

## Step by step

**1. Save the beneficiary**

Get the field template for the channel with [`GET /v2/payments/templates`](https://docs.velafi.com/api-reference/beneficiary/get-payment-templates), then save the destination with [`POST /v2/payments`](https://docs.velafi.com/api-reference/beneficiary/add-beneficiary) under the merchant that holds the balance. See [Manage Beneficiaries](https://docs.velafi.com/docs/guides/managing-beneficiaries.md) for templates per channel.

```bash cURL theme={null}
curl -X POST https://api.velafi.com/v2/payments \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": 100001,
    "paymentId": 105,
    "country": "Mexico",
    "fiat": "MXN",
    "fieldJson": {
      "Account Type": "clabe",
      "Account Number": "000000000000000001",
      "Beneficiary Name": "Example Beneficiary",
      "Bank Code": ""
    }
  }'
```

Keep the returned `id`: it is the `userPaymentId` for payouts. Pay out only to beneficiaries with `status` `1` (valid). If the account number is already saved, the response has `status` `3` and `sameAccountNumberId`; use that ID instead of creating a new record.

**2. Check the balance**

Read `balance` from [`GET /v2/merchant/accounts`](https://docs.velafi.com/api-reference/merchant/get-a-list-of-merchant-accounts) for the merchant, currency and channel. Leave room for the fee.

**3. Create the payout**

Call [`POST /v2/merchant/claimfunds`](https://docs.velafi.com/api-reference/merchant/claim-pending-fund). Always send your own unique `clientId`.

```bash cURL theme={null}
curl -X POST https://api.velafi.com/v2/merchant/claimfunds \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": 100001,
    "clientId": "payout-0001",
    "fiat": "MXN",
    "amount": 1000,
    "userPaymentId": 456
  }'
```

```json Response theme={null}
{
  "code": 200,
  "msg": "SUCCESS",
  "data": {
    "txId": 1123123123,
    "merchantId": 100001,
    "clientId": "payout-0001",
    "fiat": "MXN",
    "totalAmount": 1010,
    "amount": 1000,
    "fee": 10,
    "userPaymentId": 456,
    "createTime": "1737452292000"
  }
}
```

`amount` is what the beneficiary receives. `fee` is the payout fee set for the channel and your account, and `totalAmount` (`amount` + `fee`) is debited from the balance when the call succeeds. Amounts are rounded down to the currency's precision. A `200` response means the payout was accepted, not that it has arrived: it is pending until the webhook or the funding record says otherwise.

**4. Wait for the result**

Handle the `FUNDING_WEBHOOK` with `type` `WITHDRAW` for this `txId` (see [Webhooks to handle](#webhooks-to-handle)). If you do not receive one, read the record with [`GET /v2/merchant/funding/records`](https://docs.velafi.com/api-reference/merchant/retrieve-funding-records) using `txId` or `clientId`.

## Payout statuses

| Status | Meaning | Balance |
| - | - | - |
| `1` pending | Accepted and being sent. A record that has just been created can briefly show `0`; filtering by `status=1` returns both. | `totalAmount` is held (already debited). |
| `2` completed | The channel reported the payout as paid. | Stays debited. |
| `3` canceled | The payout failed or was rejected before completion. | `totalAmount`, fee included, is credited back. A `WITHDRAW_ROLLBACK` line appears in the fiat flows. |
| `4` refunded | A completed payout was returned to VelaFi and reversed. | `totalAmount` is credited back with a `WITHDRAW_ROLLBACK` line. |

`FUNDING_WEBHOOK` is sent for `2`, `3` and `4`, not for pending.

## Webhooks to handle

The payout event is a `FUNDING_WEBHOOK` with `type` `WITHDRAW`. `totalAmount` is negative for withdrawals.

```json FUNDING_WEBHOOK (WITHDRAW, canceled) theme={null}
{
  "eventType": "FUNDING_WEBHOOK",
  "txId": "1123123123",
  "clientId": "payout-0001",
  "merchantId": "100001",
  "fiat": "MXN",
  "totalAmount": "-1010",
  "amount": "1000",
  "fee": "10",
  "userPaymentId": "456",
  "type": "WITHDRAW",
  "status": "3",
  "failCode": "...",
  "failReason": "...",
  "bankOrderId": "...",
  "createTime": "1737452292000",
  "updateTime": "1737452350000",
  "timestamp": "1737452350512"
}
```

* Match the event to your payout by `clientId` or `txId`.
* Make the handler idempotent on `eventType` + `txId` + `status`. The same event can arrive more than once, and a payout can move from `2` to `4` later, so handle a refund after a completion.
* `failCode` and `failReason` are sent only for canceled payouts that have a recorded reason. Many failures come without one; do not depend on them to decide what to do.
* `bankOrderId` is the reference of the transfer at the payment provider, when available. Keep it for support requests.
* Return HTTP `200` with `{"callbackStatus":"SUCCESS"}` quickly. VelaFi waits at most 60 seconds; a timeout or any other response counts as a failed delivery. See [Delivery and Retries](https://docs.velafi.com/api-reference/webhooks/webhook-setup.md#delivery-and-retries).

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

## Handle failures

### Errors when creating the payout

If `claimfunds` returns an error, no funding record was created and nothing was debited. Common codes (see [Error Codes](https://docs.velafi.com/docs/error-codes.md)):

| Code | Cause | What to do |
| - | - | - |
| `-1131` | Not enough balance from this channel to cover `amount` + `fee`. | Wait for deposits, or lower the amount. |
| `-1174` | The `clientId` is already used by a funding record under your account. | The payout already exists: look it up by `clientId`. Do not create another one with the same ID. |
| `-4027` | `userPaymentId` does not exist or belongs to another account. | Check the beneficiary ID. |
| `-5017` | The beneficiary is not valid (not status `1`). | Use a valid beneficiary, or save it again. |
| `-4001` | The request failed validation, for example the merchant's currency account on that channel does not support payouts or is not active. | Check the channel against the table above and the account status. |
| `-1181` | Payouts are disabled for this merchant. | Contact VelaFi support. |
| `-5051` | The beneficiary's currency differs from `fiat`. | Use a beneficiary for the same currency. |
| `-5070` | The beneficiary does not belong to this merchant's currency account on that channel. | Save the beneficiary under the same merchant and channel as the balance. |
| `-5030` | The payout exceeds the account's withdrawal limit. | Contact VelaFi to review the limit. |
| `-5050` | The amount is below the minimum (for PEN, 10). | Raise the amount. |
| `-1146` | Another payout or transfer for the same merchant and currency was being processed. | Retry after a short wait, with the same `clientId`. |

### Timeouts and retries

The `clientId` check makes retries safe as long as you reuse the same `clientId`:

1. If the call times out or returns a network error, do not create a new `clientId`. Look up the payout with `GET /v2/merchant/funding/records?clientId=...`.
2. If a record exists, the payout was created: follow its status.
3. If none exists, send the same request again with the same `clientId`. If the first request did go through, you get `-1174` instead of a second payout.

A duplicate `clientId` returns the `-1174` error, not the original payout. Read the original from the funding records.

### Failed payouts

When a payout ends in status `3`, the full `totalAmount` is back in the balance. A `clientId` cannot be reused, even for a failed payout, so to try again:

1. Check the beneficiary details (account number, key type, holder name) and correct or re-save the beneficiary if needed.
2. Create a new payout with a **new** `clientId`, for example `payout-0001-r1`, and link it to the original in your system.

Do not retry automatically in a loop. If the same beneficiary fails repeatedly without a `failReason`, contact VelaFi support with the `txId` and `bankOrderId`.

## Move balance between merchants

To move balance from one of your merchants to another without a bank transfer, use [`POST /v2/merchant/transfer`](https://docs.velafi.com/api-reference/merchant/transfer-pending-funds). Both merchants must belong to your account and have an active currency account for the same `fiat` and `paymentId`, on a channel that supports payouts.

**1. Check the fee**

Call [`GET /v2/merchant/transfer_fee`](https://docs.velafi.com/api-reference/merchant/get-transfer-pending-funds-fee) with `fiat`. `feeType` `0` is a fixed amount and `1` is a fraction of the amount (`0.01` = 1%). The `fee` returned by the transfer itself is the amount actually charged.

**2. Create the transfer**

```bash cURL theme={null}
curl -X POST https://api.velafi.com/v2/merchant/transfer \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "transfer-0001",
    "fromMerchantId": 100001,
    "toMerchantId": 100002,
    "fiat": "MXN",
    "paymentId": 105,
    "fiatAmount": 1000
  }'
```

The sender must have `fiatAmount` + fee available from that channel. The transfer completes in the same call: the response returns `fromId` (the `TRANSFER_OUT` record on the sender) and `toId` (the `TRANSFER_IN` record on the recipient), both with status `2`. The recipient receives `fiatAmount`, and the sender is debited `totalFiatAmount`.

Transfers follow the same `clientId` rule as payouts: a `clientId` already used by any funding record under your account returns `-1174`. Always send one, so you can look the transfer up after a timeout. In the funding records, the `TRANSFER_OUT` record carries your `clientId`; the `TRANSFER_IN` record carries `TRANSFER_IN_` followed by your `clientId`. Look both up by `txId` (`fromId`, `toId`).

## Reconcile

* [`GET /v2/merchant/funding/records`](https://docs.velafi.com/api-reference/merchant/retrieve-funding-records) is the source of truth for each payout and transfer. Filter by `type=WITHDRAW` (or `TRANSFER_OUT`, `TRANSFER_IN`), `fiat` and a time window, and check that every payout you created has a final status (`2`, `3` or `4`). Investigate any that stay pending.
* [`GET /v2/merchant/fiat/flows`](https://docs.velafi.com/api-reference/merchant/get-a-list-merchant-fiat-flow) shows each balance movement: `WITHDRAW` when the payout is created, `WITHDRAW_ROLLBACK` when a canceled or refunded payout is credited back, and `TRANSFER_OUT` / `TRANSFER_IN` for transfers. The `total` on each line is the running balance after that movement.

## Test in the sandbox

Use `https://api-test.velafi.com` with sandbox credentials. Fund a test merchant with the [fiat deposit simulator](https://docs.velafi.com/docs/sandbox.md#sandbox-testing-tools), save a beneficiary, then call `claimfunds` and `transfer` against that balance. Use this to test your request validation, the error codes above and your `clientId` retry logic.

* **Webhooks do not fire in the sandbox.** Follow each payout with [List Funding Records](https://docs.velafi.com/api-reference/merchant/retrieve-funding-records.md), and test your webhook handler with sample payloads, including canceled (`3`) and refunded (`4`) withdrawals.
* The simulators cannot produce a failed or returned deposit.

## Go-live checklist

* [ ] Every payout and transfer sends a unique `clientId` that you store before making the call.
* [ ] On a timeout you look up the `clientId` before retrying, and you resend with the same `clientId`.
* [ ] A failed payout is retried only with a new `clientId`, after the beneficiary details are checked.
* [ ] Beneficiaries are saved under the same merchant and channel as the balance, and only `status` `1` beneficiaries are used.
* [ ] The balance check includes the fee.
* [ ] The `FUNDING_WEBHOOK` handler handles `WITHDRAW` with status `2`, `3` and `4`, and works when `failReason` is missing.
* [ ] A scheduled job flags payouts still pending after your expected settlement time.

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


## Related topics

- [Pay Out Local Currency Funded by Stablecoins](https://docs.velafi.com/docs/use-cases/payouts-funded-by-stablecoins.md)
- [Convert Local Currency to Stablecoins](https://docs.velafi.com/docs/use-cases/convert-to-stablecoins.md)
- [Core Use Cases](https://docs.velafi.com/docs/use-cases/overview.md)
- [Collect Local Payments with Virtual Accounts](https://docs.velafi.com/docs/use-cases/collect-with-virtual-accounts.md)
- [Agent Skills](https://docs.velafi.com/docs/developer-tools/agent-skills.md)


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