Payout V2 Integration Guide
When a BaaS merchant needs to initiate cross-border or local payouts from a fiat balance (va_group) or a crypto wallet balance (cw), the payout order can be created through /open-api/v2/payout/create. This endpoint supports channel pre-check, channel-specific party fields, source-of-funds validation for individual accounts, and multiple payout product channels.
ScopeThis guide covers the OpenAPI V2 Create Payout endpoint. Debit balances are limited to
va_group(fiat) andcw(crypto wallet);cais not supported. Payee, balance, payout detail, and file upload capabilities remain under the/open-api/v1/*paths.
Prerequisites 📋
| Item | Description |
|---|---|
| API base URL | <BASE_URL> for sandbox and production is provided by the account manager |
appid / secret | Used to obtain an access token and verify webhook signatures |
dgp-entity-id | Entity account UUID. Pass it in the header when required by the endpoint |
| Webhook URL | A publicly accessible HTTPS URL for payout status notifications |
| IP whitelist | If IP whitelisting is enabled, the caller's outbound IP must be whitelisted |
| KYC status | The corresponding entity must complete KYC and have a normal account status |
| Product activation | Payout APIs are available only to merchants with BaaS API activated |
Recommended Integration Flow ⚙️
- Obtain an access token.
- Query the supported payee options.
- Create a payee and save the
payeeId. - Upload payout materials or source-of-funds proof and save the
fileId. - Query available payout balances and save the
balanceId. - Call
POST /open-api/v2/payout/create. - If
PARTY_FIELDS_REQUIREDis returned, retry with the samecallIdand the collectedpartyFields. - If
PAYOUT_CREATEDis returned, save thepayoutTrxIdandidNo. - Track the final status through the payout detail endpoint or webhooks.
Shared V1 Endpoints (Pre-Payout Preparation) 🔍
| Endpoint | dgp-entity-id |
|---|---|
GET /open-api/v1/payout/payee-options | Not required |
POST /open-api/v1/payout/payee | Optional |
PUT /open-api/v1/payout/payee | Optional |
POST /open-api/v1/file/upload | Optional |
GET /open-api/v1/payout/accounts | Required |
POST /open-api/v2/payout/create | Required |
GET /open-api/v1/payout/detail | Required |
Query Payee Options
GET /open-api/v1/payout/payee-options
Call the Get payee options endpoint to retrieve the supported bank countries and currencies.
Create Payee
POST /open-api/v1/payout/payee
Call the Create payee endpoint to register the target account. After a successful creation, save the returned payeeId.
The dgp-entity-id header is optional for this endpoint. Pass it when the payee should be attached to a specific entity.
| Field | Required | Description |
|---|---|---|
currency | Yes | Payee currency |
country | Yes | Bank country, ISO 3166-1 alpha-2 |
firstName | Conditional | First name for an individual payee |
lastName | Yes | Last name or company name |
accountNumber | Yes | Bank account number |
bankName | Yes | Bank name |
bankCode | Conditional | Local clearing code, used for ACH / WIRE |
bicSwift | Conditional | SWIFT / BIC, required for SWIFT payouts |
payeeAddress | Yes | Payee address |
bankAddress | No | Bank address |
label | No | Label or note |
sortCode | Conditional: required for UK | UK Sort Code, required for UK payments only |
Routing parameter rules: ACH / WIRE require bankCode and accountNumber; SWIFT requires bicSwift, and providing both bankAddress and payeeAddress is recommended.
{
"currency": "USD",
"country": "US",
"firstName": "John",
"lastName": "Doe",
"accountNumber": "31139750168449",
"bankName": "JP Morgan Chase NA",
"bankCode": "028000024",
"bicSwift": "CHASUS33",
"payeeAddress": {
"line1": "123 Business Rd",
"postCode": "10001",
"city": "New York",
"state": "NY",
"country": "US"
},
"bankAddress": {
"line1": "270 Park Avenue",
"postCode": "10017",
"city": "New York",
"state": "NY",
"country": "US"
},
"label": "Supplier Q3"
}Upload Payout Materials and Source-of-Funds Proof
POST /open-api/v1/file/upload (multipart/form-data)
Call the Upload File endpoint to upload the materials. After a successful upload, save the returned fileId.
The dgp-entity-id header is optional for this endpoint.
| Form Field | Required | Description |
|---|---|---|
file | Yes | The file itself |
fileUseType | Yes | payout or source_of_funds, among others |
fileName | Yes | File name |
Response example:
{
"data": {
"fileId": "example_file_uuid"
}
}Payout proof file IDs are passed in paymentMaterial; for individual accounts, source-of-funds proof file IDs are passed in sourceOfFundsMaterial and uploaded with fileUseType=source_of_funds.
Query Available Payout Balances
GET /open-api/v1/payout/accounts
Call the Query payout accounts endpoint. The dgp-entity-id header is required.
| Response Field | Description |
|---|---|
id | Balance ID, used as balanceId when creating a payout |
availableBalance | Available balance |
currency | Currency |
type | va_group (fiat) or cw (crypto wallet) |
paymentCurrency Must Match balanceId
paymentCurrencymust match the currency of the selectedbalanceId; otherwise, the payout cannot be debited.
Create Payout Endpoint 🔗
POST /open-api/v2/payout/create
| Parameter | Location | Required | Description |
|---|---|---|---|
Authorization | header | Yes | Bearer <access_token> |
dgp-entity-id | header | Yes | Entity account UUID |
callId | body | Yes | Idempotency ID, 6~50 characters, consumed only when the payout is created |
payeeId | body | Yes | Payee ID |
receiveType | body | Yes | SWIFT / ACH / WIRE |
channelCode | body | Yes | Payout product code; only _01 codes are accepted |
balanceId | body | Yes | Debit balance ID; only va_group or cw is supported |
paymentCurrency | body | Yes | Debit currency; must match the balanceId currency |
payeeCurrency | body | Yes | Payee currency |
paymentAmount | body | One of two | Fixed payment amount |
payeeAmount | body | One of two | Fixed payee amount |
purpose | body | Yes | Payment purpose code |
paymentMaterial | body | Yes | Array of fileId values for payout proof, 1~10 files |
partyFields | body | No | Channel-specific fields; keys must match fields[].key |
reason | body | No | Note, up to 140 characters |
costBorne | body | No | SHA / OUR, applied according to channel rules |
sourceOfFunds | body | Conditional | Required for individual accounts |
sourceOfFundsMaterial | body | Conditional | Required for individual accounts; uploaded with fileUseType=source_of_funds |
Only one of paymentAmount and payeeAmount can be provided. The payment currency and payee currency may differ; the system automatically obtains a quote for cross-currency payouts.
Payout Product Codes channelCode 📦
channelCode | Internal Channel | Channel Name | Execution Mode | Notes |
|---|---|---|---|---|
va_005_01 | va_005 | Zenus | Depends on route config, mostly automatic | Existing, unchanged |
va_007_01 | va_007 | Epay | Depends on route config | Existing, unchanged |
va_008_01 | va_008 | Tazapay | Depends on route config | Existing, unchanged |
va_012_01 | va_012 | Pave | Usually offline customer operations | New, corridor aligns with Web |
va_015_01 | va_015 | QPay | Usually offline customer operations | New, corridor aligns with Web; full amount semantics |
Only Pass _01 Product CodesRequests only accept
va_005_01,va_007_01,va_008_01,va_012_01, andva_015_01. Passing internal codes such asva_012will be rejected by parameter validation.
Channel Availability Follows Route ConfigAvailable currencies, countries, and receive types align with the platform's Web payout routes. There is currently no endpoint to query available
channelCodevalues.
va_012 and va_015 Are Offline ChannelsAfter creation,
va_012_01(Pave) andva_015_01(QPay) are processed through offline customer operations. Settlement timing depends on the offline processing result and is not real-time channel payout. This document does not promise a fixed timeline.
Cost Borne 🧾
channelCode | receiveType | costBorne Behavior |
|---|---|---|
va_005_01 / va_007_01 / va_015_01 | Any | No field semantics; the value is stored as null even if provided |
va_008_01 / va_012_01 | SWIFT | SHA or OUR is allowed; omit to default to SHA; OUR charges the full-amount surcharge |
va_008_01 / va_012_01 | Non-SWIFT | Forced to SHA; OUR is not allowed |
Source of Funds for Individual Accounts 💰
sourceOfFunds | Meaning |
|---|---|
1 | Salary |
2 | Investment income |
3 | Property transfer |
4 | Self-employment income |
5 | Other |
Individual accounts must provide both sourceOfFunds and sourceOfFundsMaterial; business accounts may provide them optionally.
Response Handling 🚀
Scenario A: Party Fields Required
When data.status = PARTY_FIELDS_REQUIRED, the payout order has not been created, no funds have been debited, and the callId has not been consumed. Render the form based on the returned fields[], collect the missing values, and retry with the same callId.
{
"code": 0,
"data": {
"status": "PARTY_FIELDS_REQUIRED",
"fields": [
{
"key": "receiver_mobile",
"label": "Payee mobile",
"type": "text",
"required": true,
"order": 1
},
{
"key": "sender_beneficiary_relationship",
"label": "Relationship to payee",
"type": "select",
"required": true,
"order": 2,
"options": [
{ "label": "Family", "value": "01" },
{ "label": "Friend", "value": "02" }
]
}
]
}
}fields[].type | Value to Submit in partyFields |
|---|---|
text | String |
select | Selected option's value, not label |
date | Date string according to dateConfig.valueFormat |
file | Call /open-api/v1/file/upload first, then submit the returned fileId |
Common QPay Party Field
va_015_01pre-check may requirereceiver_bank_address, which is used to populate the receiving bank address. If this field is missing, the endpoint returnsPARTY_FIELDS_REQUIRED; no funds are debited and thecallIdis not consumed. Retry with the samecallIdafter providing the field.
Scenario B: Payout Created
{
"code": 0,
"data": {
"status": "PAYOUT_CREATED",
"payoutTrxId": "example_payout_trx_id",
"idNo": "PO202610130001",
"quoteId": "example_fx_quote_id",
"fee": 15.00,
"feeCurrency": "USD"
}
}
Check That status Is PAYOUT_CREATEDThe response must be validated by checking
data.status === "PAYOUT_CREATED". Do not judge success solely by whetherpayoutTrxIdis present. Once created, funds are locked or debited; the final result is determined by the detail endpoint or webhook.
Query and Async Notifications 🔔
| Debit Source | Query Method | Webhook Events |
|---|---|---|
Fiat va_group | GET /open-api/v1/payout/detail?payoutTrxId=... | va.transaction.create, va.transaction.update |
Crypto cw | GET /open-api/v1/payout/detail?payoutTrxId=... | crypto.wallet.transaction, crypto.wallet.transaction.update |
The dgp-entity-id header is required when querying payout details. For fiat VA payout events, refer to VA Transaction Events. For crypto wallet payout events, refer to Crypto Wallet Transaction Events.
Webhooks use HMAC signature verification. Delivery retry intervals are 0s, 10s, 30s, 60s, 2min, 5min, and 10min, up to 7 attempts. The receiver must return 2xx within 30 seconds, and processing must be idempotent.
For crypto wallet payouts, the webhook data.id is the crypto transaction ID, which differs from payoutTrxId. Use callId or payout/detail for reconciliation.
Idempotency and Concurrency 🛡️
| Mechanism | Description |
|---|---|
callId | Unique within the same entity; repeated submission after a successful creation is rejected |
| Party Fields retry | The callId is not consumed during the party-fields stage; retry until PAYOUT_CREATED |
| Concurrency | Concurrent submissions with the same callId allow only one order to take effect |
Integration Examples 🧪
Create Payout (Pave / offline)
{
"callId": "merchant-payout-demo-001",
"payeeId": "<payee-uuid>",
"receiveType": "SWIFT",
"channelCode": "va_012_01",
"balanceId": "<va_group-or-cw-balance-uuid>",
"paymentAmount": 100,
"paymentCurrency": "USD",
"payeeCurrency": "USD",
"purpose": "13",
"paymentMaterial": ["<attachment-uuid>"],
"costBorne": "OUR",
"reason": "invoice 12345"
}Create Payout (QPay; party fields may be provided after the first response)
{
"callId": "merchant-payout-demo-002",
"payeeId": "<payee-uuid>",
"receiveType": "SWIFT",
"channelCode": "va_015_01",
"balanceId": "<balance-uuid>",
"payeeAmount": 100,
"paymentCurrency": "USD",
"payeeCurrency": "USD",
"purpose": "13",
"paymentMaterial": ["<attachment-uuid>"],
"partyFields": {
"receiver_bank_address": "1 Example Street, City"
}
}receiveType 📱
| Value | Description |
|---|---|
SWIFT | International SWIFT |
ACH | US ACH |
WIRE | US Wire |
Integration Notes 🧭
- Complete end-to-end integration testing in the sandbox environment first.
- Save
payoutTrxId; CW orders cannot usevaTrxIdto query details. - Use both webhooks and
payout/detailas a double check for the final status. - Pass amounts with the correct decimal precision for the currency.
- Upload materials first to obtain
fileId, then pass them to the create payout endpoint. - Mask and redact secrets, tokens, and account information in logs.
- Follow the per-endpoint
dgp-entity-idrequirements: it is required for Create Payout, Query Accounts, and Query Payout Detail; optional for Create/Update Payee and File Upload. - If a channel combination or product code cannot be determined, compare it with the Web payout route or contact the account manager.
Core Constraints and Rules 🛡️
Internal Channel Codes Are ProhibitedInternal channel codes such as
va_012andva_015cannot be used in requests. Only_01product codes are accepted.
Updated about 16 hours ago
