This document specifies the protocols between the Airpay Platform and merchant systems. Intended for merchants who want to integrate Payout transactions through a Single API — supporting PPOB FIXED denomination, Open Denomination bank transfers, and Bill Payment (Postpaid) — all through one unified endpoint.
Supported vendors:
| Product Type | Examples |
|---|---|
| PPOB (FIXED / BILL) | Mobile Credit, PLN Token, Data Package, PLN, BPJS, PDAM, etc. |
| OPEN (Bank Transfer) | BCA, BNI, BRI, Mandiri, BNC, etc. |
Production and Sandbox use the same path prefix — differentiated by domain and API key environment setting.
| Environment | Base URL | API Key Type |
|---|---|---|
| Production | https://api.airpay.mobi |
env = 'production' |
| Sandbox | https://stagingapi.airpay.mobi |
env = 'development' |
All Payout endpoints are served under the /api/v2/ext prefix on both environments.
Sample URLs:
POST https://api.airpay.mobi/api/v2/ext/payout/order
GET https://api.airpay.mobi/api/v2/ext/payout/status
POST https://stagingapi.airpay.mobi/api/v2/ext/payout/order
GET https://stagingapi.airpay.mobi/api/v2/ext/payout/status
Before calling any Payout API, merchants must obtain an Access Token. The client_id and client_secret are provided during merchant onboarding.
1. POST /api/v2/ext/auth/token with Basic Auth (client_id:client_secret) → access_token
2. Add Authorization: Bearer <token> + apikey: <your_api_key> on every payout request.
Tokens are valid for 15 minutes. Repeated requests within the same window return the same token (deterministic).
Authenticate with Basic Auth to receive a 15-minute Bearer token.
HEADERS (BASIC AUTH):
| Name | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Required |
Basic base64(client_id:client_secret) |
curl --request POST \ --url https://api.airpay.mobi/api/v2/ext/auth/token \ --header 'Authorization: Basic <base64(id:secret)>'
{- "code": "200",
- "message": "Access Token Generated",
- "data": {
- "access_token": "eyJ1c2VySWQiOjEsImV...",
- "token_type": "Bearer",
- "expires_in": 847,
- "scope": "payout"
}
}Check merchant's payin and payout wallet balance. Both Authorization: Bearer <token> and apikey header are required.
| Authorization required | string Example: Bearer <access_token> Bearer access token obtained from the Get Access Token endpoint |
| apikey required | string Merchant API key |
curl https://api.airpay.mobi/api/v2/ext/balance-inquiry \ -H 'Authorization: Bearer <token>' \ -H 'apikey: <your_api_key>'
{- "code": "200",
- "message": "Success",
- "data": {
- "payin_balance": 5000000,
- "payout_balance": 2500000,
- "currency": "IDR",
- "retrieved_at": "2026-08-25T00:00:00+07:00"
}
}Unified endpoint for FIXED, OPEN denomination, and BILL payment. Routes automatically based on product type.
Product Type Routing
| Type | Flow |
|---|---|
| FIXED | 1 step — direct pay |
| OPEN / BILL | flag=INQ → inquiry_id → flag=PAY |
Unified endpoint for FIXED, OPEN denomination, and BILL payment. Routes automatically based on product type. Merchant must obtain an Access Token first and pass it via Authorization: Bearer <token>.
| Authorization required | string Example: Bearer <access_token> Bearer access token obtained from the Get Access Token endpoint |
| apikey required | string Merchant API key |
| flag | string Enum: "INQ" "PAY" Step control for OPEN/BILL. Default |
| product_code required | string Master product code e.g. |
| customer_number required | string Phone number, bank account number, or customer ID |
| reference_id required | string Unique merchant-generated transaction ID |
| amount | number Required for OPEN products. Transfer nominal in IDR (min 10,000) |
| inquiry_id | string Required for OPEN/BILL |
| callback_url | string URL for async webhook when transaction completes or fails |
{- "product_code": "TSEL-5000",
- "customer_number": "081234567890",
- "reference_id": "REF-FIXED-001",
}{- "code": "200",
- "message": "Order Created Successfully",
- "data": {
- "transaction_id": "TRX-REF-FIXED-001-1724505000000",
- "reference_id": "REF-FIXED-001",
- "product_code": "TSEL-5000",
- "customer_number": "081234567890",
- "price": 5500,
- "status": "PROCESS",
- "created_at": "2026-08-25T01:52:00+07:00",
- "additional_data": { }
}
}Query transaction status by transaction_id or reference_id. Also available as GET with the same fields sent as query params.
| Authorization required | string Example: Bearer <access_token> Bearer access token obtained from the Get Access Token endpoint |
| apikey required | string Merchant API key |
| transaction_id | string Airpay-generated transaction ID |
| reference_id | string Merchant-generated reference ID |
{- "reference_id": "REF-FIXED-001"
}{- "code": "200",
- "message": "Transaction Status Retrieved",
- "data": {
- "transaction_id": "TRX-REF-FIXED-001-1724505000000",
- "reference_id": "REF-FIXED-001",
- "product_code": "TSEL-5000",
- "customer_number": "081234567890",
- "price": 5500,
- "status": "SUCCESS",
- "serial_number": "SN-DGF-12345678",
- "remarks": "",
- "created_at": "2026-08-25T01:52:00+07:00",
- "additional_data": { }
}
}Postback URL: Reply URL at merchant server for receiving postback notifications from Airpay when transaction status changes.
If a transaction is FAILED, the merchant wallet is automatically refunded. Callback will still fire with status: "FAILED".
Whenever a transaction's status changes, Airpay will notify the merchant by sending an HTTP POST request with a JSON body to the postback URL registered by the merchant (see Merchant Integration Requirements above).
The merchant's endpoint is expected to respond with 200 OK once the callback has been received successfully.
This is not an Airpay endpoint — it is the URL hosted by the merchant. Airpay calls it to notify of payment status changes. Must respond with 200 OK.
| transaction_id required | string Airpay transaction ID |
| reference_id required | string Merchant reference ID |
| product_code required | string Product code |
| customer_number required | string Customer phone / account number |
| price required | number Selling price in IDR (Merchant Billing) |
| status required | string Enum: "SUCCESS" "FAILED" |
| serial_number | string Vendor SN (SUCCESS) or empty string (FAILED) |
| remarks | string Description or failure reason (Auto-Refunded on FAILED) |
| processed_at required | string ISO 8601 / RFC 3339 timestamp |
| additional_data | object Dynamic metadata object per transaction type |
{- "transaction_id": "TRX-REF-FIXED-001-1724505000000",
- "reference_id": "REF-FIXED-001",
- "product_code": "TSEL-5000",
- "customer_number": "081234567890",
- "price": 5500,
- "status": "SUCCESS",
- "serial_number": "SN-DGF-12345678",
- "remarks": "",
- "processed_at": "2026-08-25T01:53:15+07:00",
- "additional_data": { }
}Sandbox uses the exact same endpoints as Production (https://stagingapi.airpay.mobi/api/v2/ext/...) — there is no separate path. An API key with env = 'development' is what routes a request to sandbox behavior. No wallet is deducted and no real vendor is hit.
| ✓ Sandbox Does | ✗ Does NOT |
|---|---|
| Same request format as prod | Deduct wallet balance |
Returns PROCESS immediately |
Hit real vendor APIs |
Simulates SUCCESS after ~2-3s |
Process real money |
| Sends a real webhook callback | Mix with prod data |
| Isolates to a separate dev table | Auto-refund (no deduction) |
Example webhook payload sent to callback_url ~2-3s later (see Callback Notification for the field reference):
{
"transaction_id": "TRX-DEV-DEV-REF-001-...",
"reference_id": "DEV-REF-001",
"product_code": "TSEL-5000",
"customer_number": "081234567890",
"price": 5500,
"status": "SUCCESS",
"serial_number": "SN-DEV-1724505002500",
"remarks": "",
"processed_at": "2026-08-25T01:53:15+07:00",
"additional_data": { "note": "[SANDBOX] Simulated callback notification." }
}
Standard 3-digit response codes across all Airpay Payout API endpoints.
| Code | HTTP Status | Status Text | Description |
|---|---|---|---|
| 200 | 200 | OK | Request succeeded. Used for Order Create (status: PROCESS), Account Inquiry, Bill Inquiry, and Check Status. |
| 400 | 400 | Bad Request | apikey header is empty, JSON payload is invalid, a required parameter is missing (product_code, customer_number, reference_id), payout wallet balance is insufficient, or inquiry_id is invalid/expired. |
| 401 | 401 | Unauthorized | API key is invalid, expired, or not registered in the system. |
| 403 | 403 | Forbidden | API key is for the wrong environment (e.g. a production API key used on a sandbox endpoint) or the Inquiry ID does not belong to the related merchant account. |
| 404 | 404 | Not Found | Product not found / vendor not yet active, Inquiry ID data not found, or Transaction data not found on check status. |
| 422 | 422 | Unprocessable Entity | Account inquiry failed at the destination bank/e-wallet, Bill inquiry failed at the biller, or the bill is already paid (Bill already paid). |
| 500 | 500 | Internal Server Error | An internal server or database connection failure occurred. |
Three product types are supported, each with a different flow and request structure.
| Type | Flag | Steps | Example Products |
|---|---|---|---|
| FIXED | PAY (or none) |
1 step | Pulsa, Token PLN, Paket Data, Voucher Game |
| OPEN | INQ → PAY |
2 steps | PAYOUT_BCA, PAYOUT_BNI, PAYOUT_BRI, PAYOUT_MANDIRI |
| BILL | INQ → PAY |
2 steps | PLN-PASCA, BPJS, PDAM, Telkom |