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.
How it works
Before you start
- API access. An API key, your server IP on the whitelist, and a business token in the
X-BH-TOKENheader. See Authentication. - A verified merchant with an active currency account on a channel that supports payouts (see below). See Activate a Merchant Account.
- Balance on that currency account, from deposits on the same channel. See Collect Local Payments with Virtual Accounts.
- A
FUNDING_WEBHOOKendpoint registered and returning the required acknowledgement. See Webhook Setup.
Channels that support payouts from the balance
Three rules apply to every payout:
- 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.
- 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.
- Amount plus fee. The balance must cover
amountplus the payout fee.
Step by step
1
Save the beneficiary
Get the field template for the channel with Keep the returned
GET /v2/payments/templates, then save the destination with POST /v2/payments under the merchant that holds the balance. See Manage Beneficiaries for templates per channel.cURL
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 for the merchant, currency and channel. Leave room for the fee.3
Create the payout
Call
POST /v2/merchant/claimfunds. Always send your own unique clientId.cURL
Response
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). If you do not receive one, read the record with GET /v2/merchant/funding/records using txId or clientId.Payout statuses
FUNDING_WEBHOOK is sent for 2, 3 and 4, not for pending.
Webhooks to handle
The payout event is aFUNDING_WEBHOOK with type WITHDRAW. totalAmount is negative for withdrawals.
FUNDING_WEBHOOK (WITHDRAW, canceled)
- Match the event to your payout by
clientIdortxId. - Make the handler idempotent on
eventType+txId+status. The same event can arrive more than once, and a payout can move from2to4later, so handle a refund after a completion. failCodeandfailReasonare 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.bankOrderIdis the reference of the transfer at the payment provider, when available. Keep it for support requests.- Return HTTP
200with{"callbackStatus":"SUCCESS"}quickly. VelaFi waits at most 60 seconds; a timeout or any other response counts as a failed delivery. See Delivery and Retries.
Handle failures
Errors when creating the payout
Ifclaimfunds returns an error, no funding record was created and nothing was debited. Common codes (see Error Codes):
Timeouts and retries
TheclientId check makes retries safe as long as you reuse the same clientId:
- If the call times out or returns a network error, do not create a new
clientId. Look up the payout withGET /v2/merchant/funding/records?clientId=.... - If a record exists, the payout was created: follow its status.
- If none exists, send the same request again with the same
clientId. If the first request did go through, you get-1174instead of a second payout.
clientId returns the -1174 error, not the original payout. Read the original from the funding records.
Failed payouts
When a payout ends in status3, the full totalAmount is back in the balance. A clientId cannot be reused, even for a failed payout, so to try again:
- Check the beneficiary details (account number, key type, holder name) and correct or re-save the beneficiary if needed.
- Create a new payout with a new
clientId, for examplepayout-0001-r1, and link it to the original in your system.
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, usePOST /v2/merchant/transfer. 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 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
cURL
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.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/recordsis the source of truth for each payout and transfer. Filter bytype=WITHDRAW(orTRANSFER_OUT,TRANSFER_IN),fiatand a time window, and check that every payout you created has a final status (2,3or4). Investigate any that stay pending.GET /v2/merchant/fiat/flowsshows each balance movement:WITHDRAWwhen the payout is created,WITHDRAW_ROLLBACKwhen a canceled or refunded payout is credited back, andTRANSFER_OUT/TRANSFER_INfor transfers. Thetotalon each line is the running balance after that movement.
Test in the sandbox
Usehttps://api-test.velafi.com with sandbox credentials. Fund a test merchant with the fiat deposit simulator, 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, 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
clientIdthat you store before making the call. - On a timeout you look up the
clientIdbefore retrying, and you resend with the sameclientId. - 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
status1beneficiaries are used. - The balance check includes the fee.
- The
FUNDING_WEBHOOKhandler handlesWITHDRAWwith status2,3and4, and works whenfailReasonis missing. - A scheduled job flags payouts still pending after your expected settlement time.