> ## 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 Funded by Stablecoins

> Fund a merchant wallet with USDT or USDC and pay local currency, USD or EUR to a registered beneficiary with crypto_to_fiat orders, including failure handling and reconciliation.

This recipe pays fiat to a third party, such as a supplier, a seller or one of your users, and funds the payout with stablecoins. You deposit USDT or USDC into the merchant's VelaFi wallet, register the recipient's bank account as a beneficiary, and create a `crypto_to_fiat` order for each payout. VelaFi converts the crypto at the quoted rate and sends the fiat through the local rail (for example SPEI, Pix, CVU, Bre-B) or through Circle Payment Network (CPN) for USD and EUR.

For the full field reference, see the [Off-Ramp guide](https://docs.velafi.com/docs/guides/off-ramp.md) and [Create an Off-Ramp Order](https://docs.velafi.com/api-reference/order/create-a-crypto-to-fiat-order.md).

## How it works

```mermaid theme={null}
sequenceDiagram
    participant You as Your system
    participant V as VelaFi API
    participant Bank as Payout rail
    You->>V: GET /v2/assets/depositAddress
    You->>V: Send USDT/USDC on-chain to the address
    You->>V: POST /v2/payments (register beneficiary)
    V-->>You: userPaymentId, status 1
    You->>V: GET /v2/user/crypto-quote (createQuoteId=true)
    You->>V: POST /v2/order/crypto_to_fiat
    V-->>You: orderId
    Note over V: Crypto debited from the wallet (status 40)
    V->>Bank: Fiat payout to the beneficiary
    Bank-->>V: Payout result
    V-->>You: ORDER_WEBHOOK 50, then 60 (or 70 / 72)
```

## Before you start

* **API access.** An API key, your server IPs on the whitelist, and a business token for the `X-BH-TOKEN` header. See [Authentication](https://docs.velafi.com/api-reference/authentication.md).
* **A verified merchant** with an activated account for the payout currency and channel. The beneficiary must use a payment method (`paymentId`) of that account. See [Activate a Merchant Account](https://docs.velafi.com/docs/guides/activating-merchant-account.md).
* **`ORDER_WEBHOOK` registered.** Your endpoint must return HTTP `2xx` with the body `{"callbackStatus":"SUCCESS"}`, or the delivery counts as failed. See [Webhooks](https://docs.velafi.com/docs/guides/webhooks.md) and [Delivery and Retries](https://docs.velafi.com/api-reference/webhooks/webhook-setup.md#delivery-and-retries).

## Build the flow

**1. Fund the merchant's crypto wallet**

Get the merchant's deposit address for the token and network you will send:

```bash theme={null}
curl -G https://api.velafi.com/v2/assets/depositAddress \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  --data-urlencode "tokenId=USDT" \
  --data-urlencode "chainType=TRC20" \
  --data-urlencode "merchantId=100001"
```

Send the crypto to `address` (with `addressExt` if `needAddressTag` is `true`), at least `minQuantity`. See [Get Crypto Deposit Address](https://docs.velafi.com/api-reference/merchant/get-crypto-deposit-address.md) for the supported token and network pairs.

There is no webhook for incoming crypto deposits. Confirm the credit with [Get Crypto Deposit History](https://docs.velafi.com/api-reference/crypto-wallet/get-crypto-deposit-history.md) (`status` `2` is successful) and check the spendable amount in `free` from [Get Crypto Balance](https://docs.velafi.com/api-reference/merchant/get-crypto-assets-balance.md).

**2. Register the beneficiary**

Get the field template for the payout method with `GET /v2/payments/templates?paymentId={paymentId}`, then submit the filled-in fields with `POST /v2/payments`. Use the same `merchantId` as your orders.

```json Response theme={null}
{ "code": 200, "msg": "SUCCESS", "data": { "id": 456, "status": 1, "failReason": "", "sameAccountNumberId": 456 } }
```

Store `data.id`. It is the `userPaymentId` for your orders. Only a beneficiary in `status` `1` (valid) can receive payouts. `2` means verification is still in progress. `3` means it failed: read `failReason`, and if the account already exists, use `sameAccountNumberId` instead of creating a new record. Register a beneficiary once and reuse it. See [Manage Beneficiaries](https://docs.velafi.com/docs/guides/managing-beneficiaries.md).

**3. Optional: lock the rate with a quote**

Call [Get a Crypto/Fiat Quote](https://docs.velafi.com/api-reference/quote/get-user-quote-for-crypto-fiat.md) with `from` set to the crypto, `to` set to the fiat and `createQuoteId=true`. The `price` is fiat per 1 unit of crypto.

```bash theme={null}
curl "https://api.velafi.com/v2/user/crypto-quote?country=Mexico&from=USDT&to=MXN&createQuoteId=true" \
  -H "X-BH-TOKEN: YOUR_TOKEN"
```

A `quoteId` is valid for 10 seconds in production (300 seconds in the sandbox), is single-use, and is bound to the `country`, crypto and fiat it was issued for. Without a `quoteId`, the order is priced when it is created.

**4. Create the payout order**

Call `POST /v2/order/crypto_to_fiat` with the beneficiary's `userPaymentId`. Use the `country` and `fiat` of the beneficiary. Send `fiatAmount` to fix what the beneficiary receives, or `cryptoAmount` to fix what you spend. If you send both, `fiatAmount` wins. Always send a unique `clientId`.

```bash theme={null}
curl -X POST https://api.velafi.com/v2/order/crypto_to_fiat \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": 100001,
    "clientId": "payout-0001",
    "country": "Mexico",
    "fiat": "MXN",
    "crypto": "USDT",
    "fiatAmount": 1000,
    "userPaymentId": 456,
    "quoteId": "YOUR_QUOTE_ID"
  }'
```

```json Response theme={null}
{ "code": 200, "msg": "SUCCESS", "data": { "orderId": 1123123123 } }
```

**How the order is funded.** The crypto comes from the merchant's wallet balance for that `crypto`. VelaFi debits it when it creates the order, and the order moves to `40` (or `10`, `11` or `31` first when review or documents are required). If the balance is too low, the request is rejected with an insufficient-balance error and no order is created.

**Post-funded variant.** If VelaFi has enabled post-funded off-ramp for your account (it can differ by fiat currency), an underfunded order is accepted and waits in `32` (Awaiting crypto deposit). Order detail then returns `orderExpireTimestamp`, the deadline in Unix seconds. Deposit enough crypto to the wallet before the deadline. VelaFi checks the balance every minute, applies it to waiting orders oldest first, and moves a covered order to `40`. If the deadline passes first, the order is cancelled (`70`). See [Funding the order](https://docs.velafi.com/api-reference/order/create-a-crypto-to-fiat-order.md#funding-the-order).

If the request times out, look the order up by `clientId` before you retry. See [Idempotency and retries](https://docs.velafi.com/api-reference/order/create-a-fiat-to-crypto-order.md#idempotency-and-retries).

**5. Track the payout to completion**

Wait for `ORDER_WEBHOOK` events (`orderType` is `SELL`), or poll `GET /v2/order/detail?orderType=crypto_to_fiat&clientId={clientId}`.

| `orderStatus` | Meaning for a payout |
| - | - |
| `32` | Post-funded only: waiting for your crypto deposit |
| `40` | Crypto debited, fiat payout in progress |
| `50` | Payout step done, order not completed yet. On automated channels the order completes on its own. You can also complete it with [Confirm Order](https://docs.velafi.com/api-reference/order/confirm-a-specific-order.md) |
| `60` | Completed. The beneficiary was paid |
| `70` | Cancelled. The webhook also sends review and risk-review rejections as `70` |
| `80` | Rejected in review (when you poll) |
| `81` | Rejected in risk review (when you poll) |
| `72` | Refunded: the crypto went back to the wallet |

`traceNumber` holds the central bank reference number for the payout when the rail returns one. Share it with the beneficiary if they ask for proof of payment.

## USD and EUR payouts through Circle Payment Network

CPN carries USD and EUR payouts. It is payout only: you cannot collect funds or create `fiat_to_crypto` orders on CPN.

1. Activate a CPN account for the merchant and currency with `trench` set to `Circle Payment Network`. See [Activate a Merchant Account for a Business](https://docs.velafi.com/api-reference/merchant/activate-merchant-account-for-business.md).
2. Register the beneficiary on a CPN payment method, for example `120` Wire (CPN) or `127` FEDWIRE (CPN) for USD and `124` SEPA (CPN) for EUR. CPN templates ask for the beneficiary's address, a `useCase` and a `reasonForPayment` code. See the CPN templates in [Get Payment Templates](https://docs.velafi.com/api-reference/beneficiary/get-payment-templates.md).
3. Create the `crypto_to_fiat` order with `fiat` set to `USD` or `EUR` and the CPN beneficiary's `userPaymentId`, exactly as above.

Like any payout, a CPN order can go into risk review (`11`). When it does, upload the requested documents with [Upload Documents](https://docs.velafi.com/api-reference/order/upload-invoice-documents-for-a-specific-order.md). The order then moves to `12` and waits for a decision.

## Webhooks to handle

| Event | When | Key on | What to do |
| - | - | - | - |
| `ORDER_WEBHOOK` | The order enters `10`, `11`, `12`, `31`, `32`, `40`, `50`, `60`, `70` or `72` | `orderId` + `orderStatus` | Mark paid on `60`. On `70` or `72`, release the payout on your side and read `failCode` / `failReason` |

`ORDER_WEBHOOK` is sent only for orders created through the API. It does not send `71` (Cancelling) or `73` (Refunding), and orders rejected in review or risk review are sent as `70`. Events can arrive more than once and out of order, and intermediate statuses can be skipped. Deduplicate on `orderId` + `orderStatus`, and read the order with `GET /v2/order/detail` when an event does not fit the state you hold.

## Handle failures

| What you see | Likely cause | What to do |
| - | - | - |
| Create rejected with an insufficient-balance error | The wallet's `free` balance does not cover the order | Deposit more crypto, then create the order again with a new `clientId` and a new quote. |
| Create rejected for the beneficiary | `userPaymentId` is not `status` `1`, belongs to another merchant, or its currency or channel does not match the order | Check the beneficiary with [Get Beneficiary](https://docs.velafi.com/api-reference/beneficiary/get-beneficiary.md) and the merchant's activated accounts. |
| Create rejected as a repeated submission | The `clientId` was already used | Look the order up by `clientId`. Do not create it again. |
| `70` after `32` | The post-funded deadline passed before the wallet covered the order | Fund the wallet first, then create a new order. |
| `80` or `81` when you poll (`70` in the webhook) | Rejected in review or risk review | Do not resubmit unchanged. Contact VelaFi if you need the reason. |
| Order stays in `40` | The payout rail rejected or has not confirmed the payout. A rejected payout does not move the order to a final status by itself; VelaFi operations retry it or cancel the order | Contact VelaFi support with the `orderId` if the order stays in `40` longer than usual for that rail. Do not create a second order for the same payout while the first is open. |
| `60`, then `72` | The payout was returned after it completed, for example because the beneficiary's bank bounced it | The crypto is back in the merchant wallet. Correct the beneficiary details and create a new order with a new `clientId`. |

`failReason` is often empty, because many rails return no reason. Treat an empty `failReason` as "unknown", not as success. To avoid paying twice, only create a replacement payout after the original order is final (`70` or `72`).

## Reconcile

* **Each payout:** [Get Order](https://docs.velafi.com/api-reference/order/retrieve-a-specific-order.md) by `orderId` or `clientId` is the source of truth: `orderStatus`, `cryptoAmount` (debited from the wallet), `fiatAmount` (paid to the beneficiary), `fiatFee`, `orderPrice`, `orderFeeDetail`, `traceNumber`, `failCode` and `failReason`.
* **Daily totals:** [List Orders](https://docs.velafi.com/api-reference/order/retrieve-a-list-of-orders.md) with `orderType=crypto_to_fiat` and a time range. It also lists orders not created through the API.
* **Wallet:** [Get Crypto Deposit History](https://docs.velafi.com/api-reference/crypto-wallet/get-crypto-deposit-history.md) for top-ups, and [Get Crypto Balance](https://docs.velafi.com/api-reference/merchant/get-crypto-assets-balance.md) for `free` and `locked`.

Run a scheduled job that re-reads every payout still open on your side, and flag orders that stay in `40` or `50` beyond your normal payout time. Webhooks can be missed after three failed delivery attempts.

## Test in the sandbox

* Use `https://api-test.velafi.com`. Webhooks are **not** sent in the sandbox, so poll `GET /v2/order/detail`.
* Add test USDT or USDC to a merchant wallet with the crypto deposit simulator. See [Sandbox Testing Tools](https://docs.velafi.com/docs/sandbox.md#sandbox-testing-tools).
* Some payment method IDs differ in the sandbox, for example CBU/CVU (Momentum) is `18` instead of `63`. See [Payment Methods](https://docs.velafi.com/docs/payment-methods.md#sandbox-vs-production-ids).
* The simulators only produce successful deposits. You cannot simulate a bounced payout, so test your `70` and `72` handling with recorded payloads.

## Go-live checklist

* [ ] The wallet is funded before orders are created, or post-funded off-ramp is enabled and your system funds orders in `32` before `orderExpireTimestamp`.
* [ ] Beneficiaries are registered once, stored by `userPaymentId`, and only used when `status` is `1`.
* [ ] Every create request sends a unique `clientId`, and timeouts are resolved by looking up the `clientId` before retrying.
* [ ] The `ORDER_WEBHOOK` endpoint verifies the signature, returns `{"callbackStatus":"SUCCESS"}` with HTTP `2xx`, and deduplicates on `orderId` + `orderStatus`.
* [ ] `70` and `72` (including `72` after `60`) release or reverse the payout on your side, and a replacement is only created after the original is final.
* [ ] Orders stuck in `40` or `50` are flagged and escalated with the `orderId`.
* [ ] For USD and EUR, the CPN account is activated and CPN beneficiaries include the required compliance fields.

See the full [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)
- [Core Use Cases](https://docs.velafi.com/docs/use-cases/overview.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)
- [Collect Local Payments with Virtual Accounts](https://docs.velafi.com/docs/use-cases/collect-with-virtual-accounts.md)


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