<BASE_URL> as a placeholder).v1 (server version 1.2.0; every response carries the X-Api-Version header).Note: This guide does not contain the merchant real credentials or allowlist information; replace the placeholders before integrating.
/api/open/v1./api/open/v2 will be added while v1 is retained for a period, allowing merchants to migrate smoothly.GET <BASE_URL>/version to query the current server version (no authentication required).Changes in this version (1.2.0):
status no longer returns expired: unpaid timeouts are normalized to pending (processing); rely on the order query result.success); non-success states such as failed/timeout trigger no callback, and the merchant should obtain the final result via the order query API (Payout is not subject to this restriction).POST<BASE_URL>/merchant/pay/createapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| out_order_no | string | Yes | Merchant order no. (idempotency key; must be unique per merchant) |
| amount | int | Yes | Order amount (integer in minor unit, precision 0.0001; scale up by 10000, e.g. pass 12345 for 1.2345). Must be a positive JSON integer; strings (e.g. "12345") or decimals are not accepted; invalid input returns HTTP 400 (code=400). |
| currency | string | Yes | Currency (e.g. PHP/USDT; used for validation and routing) |
| pay_method | string | Yes | Payment method (provided by the platform, e.g. gcash/maya; for crypto use the chain name such as trc20/erc20) |
| country | string|null | No | Country ISO code (e.g. PH; required for fiat, optional for crypto) |
| notify_url | string | Yes | Callback URL (the platform pushes the result to this URL when the order reaches a final state) |
| return_url | string|null | No | Frontend return URL (redirect back to the merchant page after payment; whether it redirects depends on the payment method/scenario) |
| subject | string|null | No | Order subject (can be used for display or reconciliation notes) |
| remark | string|null | No | Remark (can be used for merchant custom notes / reconciliation) |
| client_ip | string|null | No | End-user IP (can be used for risk control; if omitted, the platform cannot infer the end-user origin) |
| extra | object|null | No | Extension field (top-level field, included in the signature; the object is serialized with stable JSON). ⚠️ It is strongly recommended to include the end-user info extra.customer (see the "Extension info and channel requirements" section below) |
| extra.customer | object | No | End-user info (optional). The platform no longer rejects an order due to its absence (no longer returns 300405); however, upstream channels for mainstream payment methods such as gcash/maya effectively require name/phone, and missing them will cause the upstream to reject the order and the order to fail, so it is strongly recommended to include it. When provided, the platform validates the format and auto-maps it to each channel's format (a full name is auto-split into first/last): you may provide the full name (or first_name+last_name), email, and phone |
| sign | string | Yes | Signature (computed per the documented rules; the pay endpoint uses api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/pay",
"return_url": "https://merchant.example.com/pay/result",
"subject": "Order subject (optional)",
"remark": "Remark (optional)",
"client_ip": "1.2.3.4",
"extra": {
"user_id": "u123456",
"customer": {
"first_name": "San",
"last_name": "Zhang",
"email": "[email protected]",
"phone": "13800000000"
}
}
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| order_no | string | Yes | Platform order no. (used for subsequent query and reconciliation) |
| out_order_no | string | Yes | Merchant order no. (echoed as-is, to help the merchant link its business order) |
| amount | int | Yes | Order amount (integer in minor unit, precision 0.0001; scaled up by 10000, e.g. 12345 for 1.2345) |
| currency | string | Yes | Currency (echoes the requested currency as-is, e.g. PHP/USDT) |
| pay_url | string|null | No | Payment URL (on a successful order with code=0, at least one of pay_url/qrcode_content/pay_params is non-empty; this field may be empty, in which case use the other fields to launch payment) |
| qrcode_content | string|null | No | QR code content (on a successful order with code=0, at least one of pay_url/qrcode_content/pay_params is non-empty; this field may be empty) |
| pay_params | string|null | No | Native payment string (optional): some payment methods return native app launch parameters; when pay_url is empty and this field is non-empty, use it to launch payment on the client |
| expire_at | string|null | No | Expiry time (ISO8601; nullable). Unpaid-and-timed-out is no longer represented separately as an expired state; externally it is normalized to pending (processing) — rely on the order query result. |
| status | string | Yes | Order status: pending (processing) / success / failed. Only success/failed are final states; unpaid-and-timed-out is no longer returned separately as expired and is normalized to pending. |
{
"code": 0,
"message": "ok",
"data": {
"order_no": "P20250101000001",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_url": "https://pay.example.com/h5/P20250101000001",
"qrcode_content": "https://pay.example.com/h5/P20250101000001",
"pay_params": null,
"expire_at": "2025-01-01T12:30:00Z",
"status": "pending"
}
}
POST<BASE_URL>/merchant/pay/queryapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| order_no | string|null | No | Platform order no. (provide either this or out_order_no; at least one is required) |
| out_order_no | string|null | No | Merchant order no. (provide either this or order_no; at least one is required) |
| sign | string | Yes | Signature (computed per the documented rules; the pay endpoint uses api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"out_order_no": "202501010001",
"order_no": null
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| order_no | string | Yes | Platform order no. |
| out_order_no | string | Yes | Merchant order no. |
| amount | int | Yes | Order amount (integer in minor unit, precision 0.0001; scaled up by 10000, e.g. 12345 for 1.2345) |
| currency | string | Yes | Currency |
| status | string | Yes | Order status: pending (processing) / success / failed. Only success/failed are final states; unpaid-and-timed-out is no longer returned separately as expired and is normalized to pending. |
| channel_order_no | string|null | No | Channel-side order no. (nullable) |
| paid_at | string|null | No | Payment success time (ISO8601; may be empty when not yet successful) |
| notify_status | string | Yes | Status of the platform pushing the callback to the merchant: pending/success/failed (not the same as the payment result) |
{
"code": 0,
"message": "ok",
"data": {
"order_no": "P20250101000001",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "USDT",
"status": "success",
"channel_order_no": "CH20250101XXX",
"paid_at": "2025-01-01T12:15:00Z",
"notify_status": "success"
}
}
POST<BASE_URL>/merchant/payout/createapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| out_payout_no | string | Yes | Merchant payout no. (idempotency key; must be unique per merchant) |
| amount | int | Yes | Payout amount (integer in minor unit, precision 0.0001; scale up by 10000, e.g. pass 12345 for 1.2345). Must be a positive JSON integer; strings (e.g. "12345") or decimals are not accepted; invalid input returns HTTP 400 (code=400). |
| currency | string | Yes | Currency (e.g. PHP/USDT; used for validation and routing) |
| pay_method | string | Yes | Payment method (provided by the platform, e.g. gcash/maya; for crypto use the chain name such as trc20/erc20) |
| country | string|null | No | Country ISO code (e.g. PH; required for fiat, optional for crypto) |
| notify_url | string | Yes | Callback URL (the platform pushes the result to this URL when the payout reaches a final state) |
| account_name | string|null | No | Payee name / account name (interpreted per channel type; nullable for on-chain address types) |
| account_no | string | Yes | Payee account / address (interpreted per channel type; e.g. bank card number / on-chain address) |
| bank_name | string|null | No | Bank / chain name (interpreted per channel type; nullable) |
| bank_code | string|null | No | Bank code (corresponds to the code returned by the "Query Available Banks" endpoint; the platform translates it to the upstream code and backfills bank_name; may be omitted when no bank selection is needed; if bank_name is also provided, the result resolved from bank_code takes precedence) |
| remark | string|null | No | Remark (can be used for merchant custom notes / reconciliation) |
| client_ip | string|null | No | End-user IP (can be used for risk control) |
| extra | object|null | No | Extension field (included in the signature; the object is serialized with stable JSON). ⚠️ It is strongly recommended to include the end-user info extra.customer (see the "Extension info and channel requirements" section below) |
| extra.customer | object | No | End-user info (optional). The platform no longer rejects an order due to its absence (no longer returns 300405); however, upstream channels for mainstream payment methods such as gcash/maya effectively require name/phone, and missing them will cause the upstream to reject the order and the order to fail, so it is strongly recommended to include it. When provided, the platform validates the format and auto-maps it to each channel's format (a full name is auto-split into first/last): you may provide the full name (or first_name+last_name), email, and phone |
| sign | string | Yes | Signature (computed per the documented rules; the payout endpoint uses api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"out_payout_no": "WD202501010001",
"amount": 5000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/payout",
"account_name": "San Zhang (optional)",
"account_no": "09171234567",
"bank_name": "Optional: fill in the bank name for bank-card payouts (can be omitted for wallet types such as gcash)",
"client_ip": "1.2.3.4",
"remark": "Merchant withdrawal (optional)",
"extra": {
"user_id": "u123456",
"customer": {
"first_name": "San",
"last_name": "Zhang",
"email": "[email protected]",
"phone": "13800000000"
}
}
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| payout_no | string | Yes | Platform payout no. (used for subsequent query and reconciliation) |
| out_payout_no | string | Yes | Merchant payout no. (echoed as-is, to help the merchant link its business) |
| amount | int | Yes | Payout amount (integer in minor unit, precision 0.0001; scaled up by 10000, e.g. 12345 for 1.2345) |
| currency | string | Yes | Currency (echoes the requested currency as-is, e.g. PHP/USDT) |
| status | string | Yes | Order status: pending on creation (processing; payouts always enter the processing flow first); success/failed are final states. |
| review_status | string|null | No | Payout approval status (when merchant payout approval is enabled): pending (under review) / approved / rejected; null when no approval is required. |
| fee_amount | int|null | No | Fee (nullable) |
| freeze_amount | int|null | No | Frozen amount (nullable; recommended convention amount + fee_amount) |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"amount": 5000,
"currency": "PHP",
"status": "pending",
"review_status": "pending",
"fee_amount": 20,
"freeze_amount": 5020
}
}
POST<BASE_URL>/merchant/payout/queryapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| payout_no | string|null | No | Platform payout no. (provide either this or out_payout_no; at least one is required) |
| out_payout_no | string|null | No | Merchant payout no. (provide either this or payout_no; at least one is required) |
| sign | string | Yes | Signature (computed per the documented rules; the payout endpoint uses api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"payout_no": null,
"out_payout_no": "WD202501010001"
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| payout_no | string | Yes | Platform payout no. |
| out_payout_no | string | Yes | Merchant payout no. |
| amount | int | Yes | Payout amount (integer in minor unit, precision 0.0001; scaled up by 10000, e.g. 12345 for 1.2345) |
| currency | string | Yes | Currency |
| status | string | Yes | Order status: pending (processing) / success / failed; only success/failed are final states. |
| sub_state | string|null | No | Processing sub-state (normalized): accepted / reviewing / processing (payout in progress) / verifying (payout result being verified) (non-final, keep waiting for the callback or polling); null in a final state |
| channel_order_no | string|null | No | Channel-side order no. (nullable) |
| finished_at | string|null | No | Completion time (ISO8601; may be empty when not yet in a final state) |
| failed_reason | string|null | No | Failure reason summary (may have a value only on failure) |
| notify_status | string | Yes | Status of the platform pushing the callback to the merchant: pending/success/failed (not the same as the payout result) |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"amount": 5000,
"currency": "USDT",
"status": "success",
"sub_state": null,
"channel_order_no": "CHW20250101XXX",
"finished_at": "2025-01-01T13:00:00Z",
"failed_reason": null,
"notify_status": "success"
}
}
POST<BASE_URL>/merchant/payout/banks/queryapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| pay_method | string | Yes | Payment method (bank type such as bank; wallet types such as gcash usually return an empty list) |
| country | string|null | No | Country ISO code (e.g. PH) |
| currency | string|null | No | Currency (e.g. PHP) |
| sign | string | Yes | Signature (computed per the documented rules; the payout endpoint uses api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"pay_method": "bank",
"country": "PH",
"currency": "PHP"
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| banks | array | Yes | Available bank list [{code,name}]; code is the valid value of bank_code for payout orders (a cross-channel unified code; the platform auto-translates to the upstream) |
{
"code": 0,
"message": "ok",
"data": {
"banks": [
{
"code": "BDO",
"name": "BDO Unibank"
},
{
"code": "BPI",
"name": "Bank of the Philippine Islands"
},
{
"code": "GCASH",
"name": "GCash"
},
{
"code": "MAYA",
"name": "Maya"
}
]
}
}
POST<BASE_URL>/merchant/payout/proof/queryapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| payout_no | string|null | No | Platform payout no. (provide either this or out_payout_no; at least one is required) |
| out_payout_no | string|null | No | Merchant payout no. (provide either this or payout_no; at least one is required) |
| sign | string | Yes | Signature (computed per the documented rules; the payout endpoint uses api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"payout_no": null,
"out_payout_no": "WD202501010001"
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| payout_no | string | Yes | Platform payout no. |
| out_payout_no | string | Yes | Merchant payout no. |
| proof_url | string | Yes | Payment-proof URL (has an access expiry, see expires_in; use it immediately and do not persist the URL) |
| expires_in | int|null | No | Proof URL validity (seconds, decided by the channel, e.g. 1800=30 minutes; null means the channel did not declare it) |
| queried_at | string|null | No | Query time returned by the channel (nullable) |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"proof_url": "https://proof.example.com/xxx.pdf",
"expires_in": 1800,
"queried_at": "2025-01-01 13:00:00"
}
}
POST<BASE_URL>/merchant/payout/receipt/queryapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| payout_no | string|null | No | Platform payout no. (provide either this or out_payout_no; at least one is required) |
| out_payout_no | string|null | No | Merchant payout no. (provide either this or payout_no; at least one is required) |
| lang | string|null | No | Receipt language (optional, enum: en / zh-CN / zh-TW; when omitted it is auto-derived from the order country) |
| inline | int|null | No | When set to 1, returns the base64 image data directly (image_base64 + mime); when omitted or set to 0, returns a time-limited signed download link (receipt_url) |
| sign | string | Yes | Signature (computed per the documented rules; the payout endpoint uses api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"payout_no": null,
"out_payout_no": "WD202501010001",
"lang": "zh-CN",
"inline": 0
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| payout_no | string | Yes | Platform payout no. |
| out_payout_no | string | Yes | Merchant payout no. |
| lang | string | Yes | Language actually used for the receipt (en / zh-CN / zh-TW) |
| receipt_url | string|null | No | Receipt image download URL (relative path, e.g. /api/open/v1/payout/receipt/file?token=…; the merchant must prepend its own openapi_base, i.e. the upstream agent's dedicated domain, to form the full download URL; a GET downloads it; returned only when inline=0 or omitted; the link has an expiry, see expires_in; use it immediately and do not persist the URL) |
| expires_in | int|null | No | Download link validity (seconds, e.g. 3600=1 hour; null means no declared expiry); meaningful only when inline=0 or omitted |
| mime | string|null | No | Image MIME type (e.g. image/png); returned only when inline=1 |
| image_base64 | string|null | No | Base64-encoded receipt image data (without the data URI prefix); returned only when inline=1 |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"lang": "zh-CN",
"receipt_url": "/api/open/v1/payout/receipt/file?token=eyJhbGciOiJIUzI1NiJ9...",
"expires_in": 3600
}
}
POST<BASE_URL>/merchant/pay-methods/queryapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| country | string|null | No | Country ISO code (e.g. PH; when omitted, all are returned) |
| sign | string | Yes | Signature (computed per the documented rules; this endpoint uses api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"country": "PH"
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| methods | array | Yes | Payment method list [{pay_method,name,country,currency}]; pay_method is the valid value for the order endpoints |
{
"code": 0,
"message": "ok",
"data": {
"methods": [
{
"pay_method": "gcash",
"name": "GCash",
"country": "PH",
"currency": "PHP"
},
{
"pay_method": "maya",
"name": "Maya",
"country": "PH",
"currency": "PHP"
}
]
}
}
POST<BASE_URL>/merchant/balance/queryapplication/json| Field | Type | Required | Description |
|---|---|---|---|
| merchant_no | string | Yes | Merchant no. (obtained from the merchant dashboard) |
| api_key | string | Yes | API Key (obtained from the merchant dashboard) |
| timestamp | int | Yes | Unix timestamp (seconds), used for expiry check (recommended ±300s window) |
| nonce | string|null | No | Random string for replay protection; when unused, omit it or pass null (do not pass an empty string) |
| currency | string|null | No | Currency (e.g. PHP; when omitted, all currencies are returned) |
| sign | string | Yes | Signature (computed per the documented rules; this endpoint uses api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"currency": "PHP"
}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| balances | array | Yes | Balance list [{currency,available,frozen}]; amounts are integers in the minor unit (10000 = 1 unit) |
{
"code": 0,
"message": "ok",
"data": {
"balances": [
{
"currency": "PHP",
"available": 12345600,
"frozen": 50000
}
]
}
}
GET<BASE_URL>/versionapplication/jsonNone
{}
Signature note: sign is computed over "all top-level fields of the request JSON except sign"; object/array fields are serialized with stable JSON.
{ code, message, data }; business failures usually still return HTTP 200.| Field | Type | Required | Description |
|---|---|---|---|
| major | string | Yes | Major version (path prefix, e.g. v1) |
| version | string | Yes | Semantic version (major.minor.patch) |
| base_path | string | Yes | OpenAPI path prefix |
{
"code": 0,
"message": "ok",
"data": {
"major": "v1",
"version": "1.2.0",
"base_path": "/api/open/v1"
}
}
Common fields (recommended to place in the JSON request body): merchant_no, api_key, timestamp, nonce (optional), sign.
Recommended algorithm: HMAC-SHA256. Sort all signing fields (excluding sign) in ascending ASCII order by field name, join them as key=value with &, append &secret=... at the end, run HMAC-SHA256 over the raw string, and use the lowercase hexadecimal result as sign.
A timestamp window of ±300 seconds is recommended; if nonce is used, the server performs replay protection within a short time window.
Note: the examples already apply stable JSON serialization to nested fields such as extra before signing, and can be copied as-is (usable after replacing the placeholders and credentials).
// Demo of signing and order creation. sign = HMAC-SHA256(params sorted by key as k=v joined by "&", then append "&secret=<secret>") in hex. Nested objects (extra etc.) are serialized as compact JSON with keys sorted before signing. Replace placeholders with your real credentials.
const crypto = require('crypto');
const baseUrl = '<BASE_URL>';
const merchantNo = '<YOUR_MERCHANT_NO>';
const apiKey = '<YOUR_API_KEY>';
const apiSecret = '<API_SECRET>';
const payload = {
merchant_no: merchantNo,
api_key: apiKey,
timestamp: Math.floor(Date.now() / 1000),
nonce: 'random-xyz',
out_order_no: '202501010001',
amount: 10000,
currency: 'PHP',
pay_method: 'gcash',
country: 'PH',
notify_url: 'https://merchant.example.com/api/notify/pay',
extra: {
customer: {
first_name: '<FIRST_NAME>',
last_name: '<LAST_NAME>',
email: '<EMAIL>',
phone: '<PHONE>',
},
},
};
const stableStringify = (v) => {
if (v === null) return 'null';
if (typeof v !== 'object') return JSON.stringify(v);
if (Array.isArray(v)) return '[' + v.map(stableStringify).join(',') + ']';
return '{' + Object.keys(v).sort().map((k) => JSON.stringify(k) + ':' + stableStringify(v[k])).join(',') + '}';
};
const signPayload = (data, secret) => {
const keys = Object.keys(data).filter((k) => k !== 'sign' && data[k] != null).sort();
const raw = keys.map((k) => `${k}=${typeof data[k] === 'object' && data[k] !== null ? stableStringify(data[k]) : data[k]}`).join('&') + `&secret=${secret}`;
return crypto.createHmac('sha256', secret).update(raw).digest('hex');
};
const sign = signPayload(payload, apiSecret);
const body = { ...payload, sign };
fetch(`${baseUrl}/merchant/pay/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
.then((r) => r.json())
.then((d) => console.log(d));
<?php
// Demo of signing and order creation. sign = HMAC-SHA256(params sorted by key as k=v joined by "&", then append "&secret=<secret>") in hex. Nested objects (extra etc.) are serialized as compact JSON with keys sorted before signing. Replace placeholders with your real credentials.
$baseUrl = "<BASE_URL>";
$merchantNo = "<YOUR_MERCHANT_NO>";
$apiKey = "<YOUR_API_KEY>";
$apiSecret = "<API_SECRET>";
$payload = [
'merchant_no' => $merchantNo,
'api_key' => $apiKey,
'timestamp' => time(),
'nonce' => 'random-xyz',
'out_order_no' => '202501010001',
'amount' => 10000,
'currency' => 'PHP',
'pay_method' => 'gcash',
'country' => 'PH',
'notify_url' => 'https://merchant.example.com/api/notify/pay',
'extra' => [
'customer' => [
'first_name' => '<FIRST_NAME>',
'last_name' => '<LAST_NAME>',
'email' => '<EMAIL>',
'phone' => '<PHONE>',
],
],
];
function ksort_recursive(&$a) {
if (is_array($a)) { ksort($a); foreach ($a as &$v) { ksort_recursive($v); } }
}
function stable_value($v) {
if (is_array($v)) {
ksort_recursive($v);
return json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return (string)$v;
}
ksort($payload);
$parts = [];
foreach ($payload as $k => $v) {
if ($k === 'sign' || $v === null) { continue; }
$parts[] = $k . "=" . stable_value($v);
}
$raw = implode("&", $parts) . "&secret=" . $apiSecret;
$sign = hash_hmac("sha256", $raw, $apiSecret);
$payload["sign"] = $sign;
$ch = curl_init($baseUrl . "/merchant/pay/create");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$resp = curl_exec($ch);
curl_close($ch);
echo $resp;
// Demo of signing and order creation. sign = HMAC-SHA256(params sorted by key as k=v joined by "&", then append "&secret=<secret>") in hex. Nested objects (extra etc.) are serialized as compact JSON with keys sorted before signing. Replace placeholders with your real credentials.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
String baseUrl = "<BASE_URL>";
String merchantNo = "<YOUR_MERCHANT_NO>";
String apiKey = "<YOUR_API_KEY>";
String apiSecret = "<API_SECRET>";
Map<String, Object> payload = new TreeMap<>();
payload.put("merchant_no", merchantNo);
payload.put("api_key", apiKey);
payload.put("timestamp", System.currentTimeMillis() / 1000);
payload.put("nonce", "random-xyz");
payload.put("out_order_no", "202501010001");
payload.put("amount", 10000);
payload.put("currency", "PHP");
payload.put("pay_method", "gcash");
payload.put("country", "PH");
payload.put("notify_url", "https://merchant.example.com/api/notify/pay");
Map<String, Object> extra = new TreeMap<>();
Map<String, Object> customer = new TreeMap<>();
customer.put("first_name", "<FIRST_NAME>");
customer.put("last_name", "<LAST_NAME>");
customer.put("email", "<EMAIL>");
customer.put("phone", "<PHONE>");
extra.put("customer", customer);
payload.put("extra", extra);
// signMapper: serializes nested objects with keys in ascending order, consistent with the server stable-signature convention
ObjectMapper signMapper = new ObjectMapper();
signMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
StringBuilder raw = new StringBuilder();
for (Map.Entry<String, Object> e : payload.entrySet()) {
if ("sign".equals(e.getKey()) || e.getValue() == null) { continue; }
Object val = e.getValue();
String strVal = (val instanceof Map || val instanceof java.util.Collection)
? signMapper.writeValueAsString(val) : String.valueOf(val);
raw.append(e.getKey()).append("=").append(strVal).append("&");
}
raw.append("secret=").append(apiSecret);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String sign = HexFormat.of().formatHex(mac.doFinal(raw.toString().getBytes(StandardCharsets.UTF_8)));
payload.put("sign", sign);
ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(payload);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/merchant/pay/create"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
// Demo of signing and order creation. sign = HMAC-SHA256(params sorted by key as k=v joined by "&", then append "&secret=<secret>") in hex. Nested objects (extra etc.) are serialized as compact JSON with keys sorted before signing. Replace placeholders with your real credentials.
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"time"
)
func main() {
baseUrl := "<BASE_URL>"
merchantNo := "<YOUR_MERCHANT_NO>"
apiKey := "<YOUR_API_KEY>"
apiSecret := "<API_SECRET>"
payload := map[string]any{
"merchant_no": merchantNo,
"api_key": apiKey,
"timestamp": time.Now().Unix(),
"nonce": "random-xyz",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/pay",
"extra": map[string]any{
"customer": map[string]any{
"first_name": "<FIRST_NAME>",
"last_name": "<LAST_NAME>",
"email": "<EMAIL>",
"phone": "<PHONE>",
},
},
}
// stableJSON: disables HTML escaping (Go escapes <>& by default), consistent with the semantics of JS JSON.stringify
stableJSON := func(v any) string {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.Encode(v)
return strings.TrimRight(buf.String(), "\n")
}
keys := make([]string, 0, len(payload));
for k := range payload {
if k == "sign" || payload[k] == nil { continue }
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
for i, k := range keys {
if i > 0 { b.WriteString("&") }
b.WriteString(k)
b.WriteString("=")
switch payload[k].(type) {
case map[string]any, []any:
b.WriteString(stableJSON(payload[k]))
default:
b.WriteString(fmt.Sprint(payload[k]))
}
}
b.WriteString("&secret=")
b.WriteString(apiSecret)
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(b.String()))
sign := hex.EncodeToString(mac.Sum(nil))
payload["sign"] = sign
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseUrl+"/merchant/pay/create", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}
# Demo of signing and order creation. sign = HMAC-SHA256(params sorted by key as k=v joined by "&", then append "&secret=<secret>") in hex. Nested objects (extra etc.) are serialized as compact JSON with keys sorted before signing. Replace placeholders with your real credentials.
import time
import hmac
import hashlib
import json
import requests
base_url = "<BASE_URL>"
merchant_no = "<YOUR_MERCHANT_NO>"
api_key = "<YOUR_API_KEY>"
api_secret = "<API_SECRET>"
payload = {
"merchant_no": merchant_no,
"api_key": api_key,
"timestamp": int(time.time()),
"nonce": "random-xyz",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/pay",
"extra": {
"customer": {
"first_name": "<FIRST_NAME>",
"last_name": "<LAST_NAME>",
"email": "<EMAIL>",
"phone": "<PHONE>",
},
},
}
def _sv(v):
"""Stable serialization: nested object/array -> compact JSON with keys in ascending order; scalar -> str(v)."""
if isinstance(v, (dict, list)):
return json.dumps(v, sort_keys=True, separators=(',', ':'), ensure_ascii=False)
return str(v)
keys = sorted(k for k in payload if k != 'sign' and payload[k] is not None)
raw = "&".join(f"{k}={_sv(payload[k])}" for k in keys) + f"&secret={api_secret}"
sign = hmac.new(api_secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
payload["sign"] = sign
resp = requests.post(f"{base_url}/merchant/pay/create", json=payload)
print(resp.text)
All amounts are expressed as integers in the "minor accounting unit"; the current precision convention is 0.0001, so multiply values by 10000, e.g. pass 12345 for 1.2345, to avoid floating-point errors.
The platform sends the collection/payout result via POST application/json to the notify_url provided when the order was created.
The callback also carries signature fields; the merchant should verify the signature using the respective secret for the corresponding business (pay/payout).
Collection callback timing: Collection sends a callback to the merchant only when the order succeeds (status=success); non-success states such as failed/timeout trigger no callback, and the merchant should obtain the final result via the order query API (pay/query).
(Payout is not subject to this restriction: Payout sends a callback at both final states success/failed.)
Success acknowledgement rule: After processing successfully, the merchant must return HTTP 200 with a response body of success or ok (case-insensitive);
JSON formats are also accepted: {"success":true} / {"code":0} / {"message":"success"} / {"message":"ok"}.
If the response does not follow this rule (even an HTTP 200 with a different body, such as an empty response or an HTML error page), the platform treats the callback as failed,
retries it per policy, and triggers an alert when consecutive failures reach the threshold.
The following error codes are common to all authenticated endpoints; the "Common Errors" of each endpoint lists only high-frequency or business-related items and does not imply that other authentication codes will not be returned:
100001 Parameter validation failed (HTTP 200; message is the raw Joi field error, e.g. "amount" must be a number)100101 Request expired (timestamp outside the allowed window)100102 IP not in the allowlist100103 Replay (duplicate nonce/signature fingerprint)100104 Invalid signature100105 IP in the blocklist100106 Too many authentication failures (rate limited)