Recipe: pay your end users from your sales (disbursements)
Collect with VEXPay, credit each of your end users (staff, contractors, affiliates) in dollars at the BCV rate every pay period, let them withdraw to their own Pago Móvil from your own app, and give each one a monthly statement. You pay the withdrawal fee.
What you will build
Your business sells through VEXPay payment links, checkout sessions or charges, and the money lands in your balance. Every pay period (weekly, biweekly, monthly) your admin system credits each end user a dollar amount; VEXPay converts it at the official BCV rate and keeps it in that person's own balance. When a person wants their money, they press Withdraw in your app, your server asks VEXPay for the payout, and the bolívares arrive on their Pago Móvil. Your end users never sign in to VEXPay.
| Money | Where it sits | Who can move it |
|---|---|---|
| Your sales | Your balance (held 24h, then available) | You: credit end users or withdraw to your own bank |
| A credit to an end user | That end user's balance (pendingVes, then availableVes) | You, on the end user's behalf: withdraw to their verified Pago Móvil |
| The withdrawal fee | Charged to your balance on each withdrawal | VEXPay |
How the pieces fit
| Your system | VEXPay API |
|---|---|
| A person joins (employee, affiliate…) | POST /v1/merchants + add and verify their Pago Móvil |
| Pay-period run: "pay $100 to each person" | POST /v1/merchants/:id/transfers with amountUsd, one per person |
| Person's balance screen | GET /v1/merchants/:id/balance |
| Person presses Withdraw | POST /v1/payouts with fromMerchantBalance: true |
| History / monthly statement | GET /v1/merchants/:id/transactions, GET /v1/merchants/:id/statement?month=YYYY-MM |
| Status updates | Webhooks: merchant.balance.updated, payout.completed, payout.failed |
- Keep your API key on your server only, never in a mobile app or browser.
- Store the VEXPay
merchantIdnext to each person in your database; use your own id asexternalRef. - In the API an end user is a
merchant: the same object marketplaces use for their sellers.
Step 01
Collect your sales
Sell as you do today: portal payment links, checkout sessions or API charges. Each completed sale adds its net (after the VEXPay processing fee) to your balance. Live sales are held for 24 hours (heldVes) before they are available; your balance and every movement are at GET /v1/balance and GET /v1/balance/transactions.
Step 02
Register each end user and verify their Pago Móvil
# 1. Create the end user (externalRef = your own id for the person)
curl --request POST "${API_URL}/v1/merchants" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{ "externalRef": "emp-104", "name": "Ana Rodriguez", "identification": "V12345678", "contactEmail": "ana@mail.com", "contactPhone": "04141234567" }'
# 2. Add their Pago Móvil
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{ "bankCode": "0105", "phone": "04141234567" }'
# 3. Two micro-deposits, then confirm the amounts the person received
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/verify/start" --header "x-api-key: ${VEXPAY_API_KEY}"
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/verify" \
--header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{ "amount1": "0.37", "amount2": "0.82" }'Ask the person, inside your app, for the two small amounts they received. Money only ever leaves to a verified method. Full detail: Onboard a seller.
Step 03
Run the pay period: credit each person in dollars
For each person, send the dollar amount and a transferKey that is unique per person and pay period. VEXPay converts at the BCV rate of that moment, debits your balance and credits the person. If your job crashes and runs again, the same transferKey returns the first result: nobody is paid twice and nobody is re-priced.
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/transfers" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{ "amountUsd": "100.00", "transferKey": "2026-11-q1-emp-104" }'
# 200 →
# { "pendingVes": "0.00", "availableVes": "21540.00", ...,
# "transfer": { "transferKey": "2026-11-q1-emp-104", "amountVes": "21540.00",
# "amountUsd": "100.00", "rate": "215.4000", "rateSource": "bcv",
# "rateAsOf": "2026-11-15T13:00:00.000Z" } }const period = '2026-11-q1';
for (const person of await db.people.activeWithVexpayId()) {
const res = await fetch(`${process.env.VEXPAY_API_URL}/v1/merchants/${person.vexpayMerchantId}/transfers`, {
method: 'POST',
headers: { 'content-type': 'application/json', 'x-api-key': process.env.VEXPAY_API_KEY },
body: JSON.stringify({ amountUsd: person.amountUsd, transferKey: `${period}-${person.id}` }),
});
if (res.status === 503) throw new Error('BCV rate unavailable, retry the run later'); // safe to rerun
if (!res.ok) { await db.payRun.fail(person.id, await res.json()); continue; }
const { transfer } = await res.json();
await db.payRun.record(person.id, transfer); // keep amountVes + rate for your books
}- Prefer to set bolívares yourself? Send
amountVesinstead ofamountUsd(one or the other). - Your balance must cover the run: check
GET /v1/balancefirst. A credit you cannot cover answers 422insufficient_balanceand records nothing. - Credits paid with sales still in their 24-hour hold wait in the person's
pendingVesuntil that hold ends. Running the job the day after the sales came in avoids the wait.
Step 04
Let each person withdraw from your app
Show the person their availableVes (from GET /v1/merchants/:id/balance). When they ask for a withdrawal, your server sends the payout. They receive exactly monto; the withdrawal fee is charged to your balance.
curl --request POST "${API_URL}/v1/payouts" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{ "merchantId": "MERCHANT_ID", "monto": "20000.00", "concepto": "Retiro", "externalRef": "wd-emp-104-0007", "fromMerchantBalance": true }'
# GET /v1/payouts/:id →
# { "status": "completed", "monto": "20000.00", "feeVes": "253.85", "feePayer": "tenant", ... }| Example fee: 1% (min $0.25) + $0.25, BCV 215.40 | You pay |
|---|---|
| Withdrawal of Bs 20,000.00 | Bs 200.00 + Bs 53.85 = Bs 253.85 |
| Withdrawal of Bs 2,000.00 | Bs 53.85 (minimum) + Bs 53.85 = Bs 107.70 |
- Use a new
externalRefper withdrawal request and reuse it on retries: a retry never pays twice. - Keep a small reserve in your own balance for fees. Without it the withdrawal is refused with 422
insufficient_balanceandreason: "tenant_fee_unfunded". - Listen for
payout.completedandpayout.failed. If the bank rejects a withdrawal, the money goes back to the person's available balance and the fee back to yours. - Optionally let people decide how much to withdraw, or withdraw their whole
availableVeswith one tap.
Step 05
Monthly statements and your own export
Each person's statement shows the opening balance, every credit (with the dollar amount and BCV rate), release and withdrawal in the month, and the closing balance. Months follow Venezuela time. Opening plus the movements always equals closing.
# JSON, for your own screens
curl "${API_URL}/v1/merchants/MERCHANT_ID/statement?month=2026-11" --header "x-api-key: ${VEXPAY_API_KEY}"
# CSV, to send to the person or your accountant
curl "${API_URL}/v1/merchants/MERCHANT_ID/statement?month=2026-11&format=csv" \
--header "x-api-key: ${VEXPAY_API_KEY}" --output emp-104-2026-11.csv
# Full history, newest first, paged
curl "${API_URL}/v1/merchants/MERCHANT_ID/transactions?limit=50" --header "x-api-key: ${VEXPAY_API_KEY}"No code needed for the monthly close: Portal → Merchants → Monthly statement downloads one person, or every person in one CSV with their name and ID on each row. Your own side (sales, credits, fees) is in GET /v1/balance/transactions: credits show as seller_transfer and fees as disbursement_payout_fee.
Go-live checklist
- Run the whole flow with your Test key: a sale, a dollar credit, a withdrawal, a statement (Test everything first).
- Tell VEXPay your number of people and the usual amount per pay period, so the payout limit for new accounts (USD 300 per rolling 24 hours in the first 30 days) is set to fit your payroll. Over it, withdrawals wait in
awaiting_approvalfor review. - Agree your withdrawal fee price with VEXPay.
- Register a webhook endpoint and handle
payout.completed/payout.failed(Set up webhooks). - Make your pay-period job safe to rerun (same
transferKey) and your withdrawals safe to retry (sameexternalRef).
Questions
| Question | Answer |
|---|---|
| Do my end users need a VEXPay account? | No. Your system calls the API with your key; they only see your app and their Pago Móvil. |
| Which rate is used? | The official BCV rate when the credit is made, saved with it. The withdrawal fee's dollar parts use the BCV rate when the withdrawal is made. |
| Can I take back a credit? | Not through the API. Money you credit belongs to the person until they withdraw it; credit only what you owe. Contact VEXPay for mistakes. |
| What if a customer disputes a card sale after I paid my people? | The refund or chargeback is taken from your balance, not from your end users. Recovering it from them is up to you. |
| Can I pay to a bank account instead of Pago Móvil? | Withdrawals go to the person's verified Pago Móvil. |
| Do I pay a fee when I credit someone? | No. You pay the processing fee on your sales and the withdrawal fee when a person withdraws. |
Related: Pay your end users · Onboard a seller · Merchants & payouts · Read balance & holds