Payment Infrastructure
for Southeast Asia
Integrate payin and payout capabilities with a single, unified API. RSA-secured, real-time webhooks, 150+ Indonesian banks supported.
Core Capabilities
Payin (Collection)
Accept payments via bank transfer, QRIS, and e-wallets. Hosted cashier page with configurable redirect.
Payout (Disbursement)
Send funds to 150+ Indonesian banks and e-wallets. Flexible fee handling with real-time status tracking.
RSA Encryption
Every request is signed with RSA-2048. Callback verification ensures data integrity end-to-end.
Authentication
All API requests must include two headers for authentication. The signature is generated using RSA-2048 with SHA-256 hashing.
| Header | Type | Description |
|---|---|---|
ONEPAY-SIGN |
string | RSA signature of the request body (Base64 encoded) |
ONEPAY-MCODE |
string | Your merchant code (e.g. MT182504LVhvaB) |
Signature Generation
key=value pairs joined by &// Sorted parameter string:
amount=1000000&email=t.wcvji@ynnid.bn&merchantOid=7438bb79-...&name=test1¬ifyUrl=http://...&phone=18144528972&redirectUrl=https://...×tamp=12312311&type=payin&uid=28835218241114223x
// SHA-256 hash:
383fb0663e87ebae7c5e1debb68746eab0f73a16ec82be3d5ca388bec0254691
// RSA-signed (Base64):
a/WAwsxJQvYbfN18DHmK93RQbBQAQXLpETCBzrkK8a1cy+6M9BDX...=
Quick Start
Create your first payin order in minutes.
curl -X POST https://api.onepay.vip/api/v1/payin/create \
-H "Content-Type: application/json" \
-H "ONEPAY-SIGN: <your_rsa_signature>" \
-H "ONEPAY-MCODE: MT182504LVhvaB" \
-d '{
"uid": "user_2883",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": 1745723235,
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"passage": "",
"type": "payin"
}'
import crypto from 'crypto';
const createPayin = async () => {
const body = {
uid: "user_2883",
merchantOid: crypto.randomUUID(),
amount: 1000000,
notifyUrl: "https://your-server.com/callback/payin",
redirectUrl: "https://your-app.com/payment/success",
timestamp: Math.floor(Date.now() / 1000),
name: "Taylor",
email: "taylor@example.com",
phone: "08123456789",
passage: "",
type: "payin"
};
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://api.onepay.vip/api/v1/payin/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
"ONEPAY-SIGN": sign,
"ONEPAY-MCODE": "MT182504LVhvaB"
},
body: JSON.stringify(body)
});
return res.json(); // { code: 0, data: { oid, merchantOid, url } }
};
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"net/http"
)
func createPayin() {
body := map[string]interface{}{
"uid": "user_2883",
"merchantOid": uuid.New().String(),
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": time.Now().Unix(),
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"type": "payin",
}
sorted := jsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sign, _ := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sign)
req, _ := http.NewRequest("POST",
"https://api.onepay.vip/api/v1/payin/create", body)
req.Header.Set("ONEPAY-SIGN", signB64)
req.Header.Set("ONEPAY-MCODE", "MT182504LVhvaB")
}
import hashlib, base64, json, time, uuid, requests
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
def create_payin():
body = {
"uid": "user_2883",
"merchantOid": str(uuid.uuid4()),
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": int(time.time()),
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"type": "payin"
}
# Sort, hash, sign
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post(
"https://api.onepay.vip/api/v1/payin/create",
json=body,
headers={
"ONEPAY-SIGN": sign_b64,
"ONEPAY-MCODE": "MT182504LVhvaB"
}
)
return resp.json()
import java.security.*;
import java.util.*;
public class OnePay {
public static String createPayin() throws Exception {
Map<String, Object> body = new TreeMap<>();
body.put("uid", "user_2883");
body.put("merchantOid", UUID.randomUUID().toString());
body.put("amount", 1000000);
body.put("notifyUrl", "https://your-server.com/callback/payin");
body.put("redirectUrl", "https://your-app.com/payment/success");
body.put("timestamp", System.currentTimeMillis() / 1000);
body.put("name", "Taylor");
body.put("email", "taylor@example.com");
body.put("phone", "08123456789");
body.put("type", "payin");
// Sign: sort → SHA256 → RSA PKCS1v15 → Base64
String sorted = body.entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST with headers ONEPAY-SIGN + ONEPAY-MCODE
return httpPost("https://api.onepay.vip/api/v1/payin/create",
body, signB64, "MT182504LVhvaB");
}
}
$body = [
"uid" => "user_2883",
"merchantOid" => sprintf("%s-%s-%s-%s-%s", ...), // UUID
"amount" => 1000000,
"notifyUrl" => "https://your-server.com/callback/payin",
"redirectUrl" => "https://your-app.com/payment/success",
"timestamp" => time(),
"name" => "Taylor", "email" => "taylor@example.com",
"phone" => "08123456789", "type" => "payin"
];
$sign = OnePay::sign($body, $privateKeyPem);
$ch = curl_init("https://api.onepay.vip/api/v1/payin/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"ONEPAY-SIGN: " . $sign,
"ONEPAY-MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
{
"code": 0,
"data": {
"oid": "CLN270425tR53wb2W",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"url": "https://cashier.onepay.vip/index.html?oid=CLN270425tR53wb2W"
},
"msg": "Operation successful"
}
API Reference
Base URL: https://api.onepay.vip/api/v1
/payin/create
Create a payin (collection) order. Returns a cashier URL for the user to complete payment.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
uid | string | Yes | User identifier |
merchantOid | string | Yes | Unique merchant order ID (UUID recommended) |
amount | integer | Yes | Amount in cents (e.g. 1000000 = IDR 10,000) |
notifyUrl | string | Yes | Webhook URL for async notifications |
redirectUrl | string | Yes | Redirect URL after payment |
timestamp | integer | Yes | Unix timestamp (seconds) |
name | string | Yes | Customer name |
email | string | Yes | Customer email |
phone | string | Yes | Customer phone |
passage | string | No | Payment channel code. If set, skips channel selection on cashier |
type | string | Yes | Fixed: "payin" |
Response
| Field | Type | Description |
|---|---|---|
code | integer | 0 on success |
data.oid | string | OnePay system order ID |
data.merchantOid | string | Your merchant order ID (echo back) |
data.url | string | Cashier page URL — redirect the user here |
msg | string | "Operation successful" |
Code Examples
curl -X POST https://api.onepay.vip/api/v1/payin/create \
-H "Content-Type: application/json" \
-H "ONEPAY-SIGN: <your_rsa_signature>" \
-H "ONEPAY-MCODE: YOUR_MERCHANT_CODE" \
-d '{
"uid": "user_2883",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"amount": 1000000,
"notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": 1745723235,
"name": "Taylor",
"email": "taylor@example.com",
"phone": "08123456789",
"passage": "",
"type": "payin"
}'
const createPayin = async () => {
const body = {
uid: "user_2883",
merchantOid: crypto.randomUUID(),
amount: 1000000,
notifyUrl: "https://your-server.com/callback/payin",
redirectUrl: "https://your-app.com/payment/success",
timestamp: Math.floor(Date.now() / 1000),
name: "Taylor", email: "taylor@example.com",
phone: "08123456789", passage: "", type: "payin"
};
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://api.onepay.vip/api/v1/payin/create", {
method: "POST",
headers: { "Content-Type": "application/json", "ONEPAY-SIGN": sign, "ONEPAY-MCODE": MCODE },
body: JSON.stringify(body)
});
return res.json();
};
body := map[string]interface{}{
"uid": "user_2883", "merchantOid": uuid.New().String(),
"amount": 1000000, "notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": time.Now().Unix(), "name": "Taylor",
"email": "taylor@example.com", "phone": "08123456789",
"type": "payin",
}
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sig)
req, _ := http.NewRequest("POST", "https://api.onepay.vip/api/v1/payin/create", jsonBody)
req.Header.Set("ONEPAY-SIGN", signB64)
req.Header.Set("ONEPAY-MCODE", mcode)
body = {
"uid": "user_2883", "merchantOid": str(uuid.uuid4()),
"amount": 1000000, "notifyUrl": "https://your-server.com/callback/payin",
"redirectUrl": "https://your-app.com/payment/success",
"timestamp": int(time.time()), "name": "Taylor",
"email": "taylor@example.com", "phone": "08123456789",
"type": "payin"
}
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post("https://api.onepay.vip/api/v1/payin/create",
json=body, headers={"ONEPAY-SIGN": sign_b64, "ONEPAY-MCODE": MCODE})
Map<String, Object> body = new TreeMap<>();
body.put("uid", "user_2883");
body.put("merchantOid", UUID.randomUUID().toString());
body.put("amount", 1000000);
body.put("notifyUrl", "https://your-server.com/callback/payin");
body.put("redirectUrl", "https://your-app.com/payment/success");
body.put("timestamp", System.currentTimeMillis() / 1000);
body.put("name", "Taylor"); body.put("email", "taylor@example.com");
body.put("phone", "08123456789"); body.put("type", "payin");
String sorted = body.entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey); sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST with headers ONEPAY-SIGN + ONEPAY-MCODE
$body = [
"uid" => "user_2883",
"merchantOid" => sprintf("%s-%s-%s-%s-%s", ...), // UUID
"amount" => 1000000,
"notifyUrl" => "https://your-server.com/callback/payin",
"redirectUrl" => "https://your-app.com/payment/success",
"timestamp" => time(),
"name" => "Taylor", "email" => "taylor@example.com",
"phone" => "08123456789", "type" => "payin"
];
$sign = OnePay::sign($body, $privateKeyPem);
$ch = curl_init("https://api.onepay.vip/api/v1/payin/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"ONEPAY-SIGN: " . $sign,
"ONEPAY-MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
{
"code": 0,
"data": {
"oid": "CLN270425tR53wb2W",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"url": "https://cashier.onepay.vip/index.html?oid=CLN270425tR53wb2W"
},
"msg": "Operation successful"
}
/payout/create
Create a payout (disbursement) order to send funds to a bank account or e-wallet.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
uid | string | Yes | User identifier |
name | string | Yes | Beneficiary name |
merchantOid | string | Yes | Unique merchant order ID |
amount | integer | Yes | Amount in cents |
bankCode | string | Yes | Bank code (see Bank Codes) |
accountNo | string | Yes | Beneficiary account number |
email | string | No | Beneficiary email |
notifyUrl | string | Yes | Webhook URL for status updates |
note | string | No | Remark / description |
feeType | string | Yes | "Additional" (fee on top) or "Include" (fee deducted from amount) |
passage | string | Yes | Payment channel code |
type | string | Yes | Fixed: "payout" |
Code Examples
curl -X POST https://api.onepay.vip/api/v1/payout/create \
-H "Content-Type: application/json" \
-H "ONEPAY-SIGN: <your_rsa_signature>" \
-H "ONEPAY-MCODE: YOUR_MERCHANT_CODE" \
-d '{
"uid": "3800",
"name": "John Doe",
"notifyUrl": "https://your-server.com/callback/payout",
"amount": 1500000,
"merchantOid": "d27eb86d-ada8-43cb-9d31-fa87dd578ab7",
"bankCode": "014",
"accountNo": "1234567890",
"email": "john@example.com",
"note": "Withdrawal request",
"type": "payout",
"feeType": "Additional",
"passage": "your_passage_code"
}'
const createPayout = async () => {
const body = {
uid: "3800", name: "John Doe",
notifyUrl: "https://your-server.com/callback/payout",
amount: 1500000,
merchantOid: crypto.randomUUID(),
bankCode: "014", // BCA
accountNo: "1234567890",
email: "john@example.com",
note: "Withdrawal request",
type: "payout",
feeType: "Additional",
passage: "your_passage_code"
};
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://api.onepay.vip/api/v1/payout/create", {
method: "POST",
headers: { "Content-Type": "application/json", "ONEPAY-SIGN": sign, "ONEPAY-MCODE": MCODE },
body: JSON.stringify(body)
});
return res.json();
};
body := map[string]interface{}{
"uid": "3800", "name": "John Doe",
"notifyUrl": "https://your-server.com/callback/payout",
"amount": 1500000,
"merchantOid": uuid.New().String(),
"bankCode": "014", // BCA
"accountNo": "1234567890",
"email": "john@example.com",
"note": "Withdrawal request",
"type": "payout",
"feeType": "Additional",
"passage": "your_passage_code",
}
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sig)
req, _ := http.NewRequest("POST", "https://api.onepay.vip/api/v1/payout/create", jsonBody)
req.Header.Set("ONEPAY-SIGN", signB64)
req.Header.Set("ONEPAY-MCODE", mcode)
body = {
"uid": "3800", "name": "John Doe",
"notifyUrl": "https://your-server.com/callback/payout",
"amount": 1500000,
"merchantOid": str(uuid.uuid4()),
"bankCode": "014", # BCA
"accountNo": "1234567890",
"email": "john@example.com",
"note": "Withdrawal request",
"type": "payout",
"feeType": "Additional",
"passage": "your_passage_code"
}
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post("https://api.onepay.vip/api/v1/payout/create",
json=body, headers={"ONEPAY-SIGN": sign_b64, "ONEPAY-MCODE": MCODE})
Map<String, Object> body = new TreeMap<>();
body.put("uid", "3800"); body.put("name", "John Doe");
body.put("notifyUrl", "https://your-server.com/callback/payout");
body.put("amount", 1500000);
body.put("merchantOid", UUID.randomUUID().toString());
body.put("bankCode", "014"); // BCA
body.put("accountNo", "1234567890");
body.put("email", "john@example.com");
body.put("note", "Withdrawal request");
body.put("type", "payout");
body.put("feeType", "Additional");
body.put("passage", "your_passage_code");
String sorted = body.entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey); sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST with headers ONEPAY-SIGN + ONEPAY-MCODE
$body = [
"uid" => "3800", "name" => "John Doe",
"notifyUrl" => "https://your-server.com/callback/payout",
"amount" => 1500000,
"merchantOid" => uniqid("", true),
"bankCode" => "014", // BCA
"accountNo" => "1234567890",
"email" => "john@example.com",
"note" => "Withdrawal request",
"type" => "payout",
"feeType" => "Additional",
"passage" => "your_passage_code"
];
$sign = OnePay::sign($body, $privateKeyPem);
$ch = curl_init("https://api.onepay.vip/api/v1/payout/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"ONEPAY-SIGN: " . $sign,
"ONEPAY-MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
{
"code": 0,
"data": {
"oid": "PAY200325mb7gKfOI",
"merchantOid": "d27eb86d-ada8-43cb-9d31-fa87dd578ab7"
},
"msg": "Operation successful"
}
/query/order
Query the status of a payin or payout order. Provide either oid or merchantOid.
| Field | Type | Description |
|---|---|---|
type | string | "payin" or "payout" |
oid | string | OnePay system order ID (either oid or merchantOid) |
merchantOid | string | Your merchant order ID (either oid or merchantOid) |
Code Examples
curl -X POST https://api.onepay.vip/api/v1/query/order \
-H "Content-Type: application/json" \
-H "ONEPAY-SIGN: <your_rsa_signature>" \
-H "ONEPAY-MCODE: YOUR_MERCHANT_CODE" \
-d '{
"type": "payin",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101"
}'
const queryOrder = async (type, merchantOid) => {
const body = { type, merchantOid };
const sign = generateRSASignature(body, privateKey);
const res = await fetch("https://api.onepay.vip/api/v1/query/order", {
method: "POST",
headers: { "Content-Type": "application/json", "ONEPAY-SIGN": sign, "ONEPAY-MCODE": MCODE },
body: JSON.stringify(body)
});
return res.json();
};
// Usage
const result = await queryOrder("payin", "9d6e7671-64b9-408b-abad-1ef4601af101");
body := map[string]interface{}{
"type": "payin",
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
}
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
req, _ := http.NewRequest("POST", "https://api.onepay.vip/api/v1/query/order", jsonBody)
req.Header.Set("ONEPAY-SIGN", base64.StdEncoding.EncodeToString(sig))
req.Header.Set("ONEPAY-MCODE", mcode)
def query_order(order_type, merchant_oid):
body = {"type": order_type, "merchantOid": merchant_oid}
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if v)
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.post("https://api.onepay.vip/api/v1/query/order",
json=body, headers={"ONEPAY-SIGN": sign_b64, "ONEPAY-MCODE": MCODE})
return resp.json()
# Usage
result = query_order("payin", "9d6e7671-64b9-408b-abad-1ef4601af101")
Map<String, Object> body = new TreeMap<>();
body.put("type", "payin");
body.put("merchantOid", "9d6e7671-64b9-408b-abad-1ef4601af101");
String sorted = body.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey); sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
// POST to /query/order with headers ONEPAY-SIGN + ONEPAY-MCODE
$body = [
"type" => "payin",
"merchantOid" => "9d6e7671-64b9-408b-abad-1ef4601af101"
];
$sign = OnePay::sign($body, $privateKeyPem);
$ch = curl_init("https://api.onepay.vip/api/v1/query/order");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"ONEPAY-SIGN: " . $sign,
"ONEPAY-MCODE: " . $mcode
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
// Payin order query response
{
"code": 0,
"data": {
"amount": 1000000,
"merchantOid": "9d6e7671-64b9-408b-abad-1ef4601af101",
"orderOid": "CLN270425tR53wb2W",
"timestamp": 1745723300,
"status": "order_success",
"settleStatus": "settle_success"
},
"msg": "Operation successful"
}
/query/balance
Query merchant account balance. For this endpoint, sign the ONEPAY-MCODE value itself.
Response
| Field | Type | Description |
|---|---|---|
code | integer | Status code (0 = success) |
amount | integer | Available balance (cents) |
settleAmount | integer | Pending settlement balance (cents) |
freezeAmount | integer | Frozen balance (cents) |
Code Examples
# Note: ONEPAY-SIGN is the RSA signature of your MCODE value itself
curl -X GET https://api.onepay.vip/api/v1/query/balance \
-H "ONEPAY-SIGN: <rsa_sign_of_mcode>" \
-H "ONEPAY-MCODE: YOUR_MERCHANT_CODE"
const queryBalance = async () => {
// For balance query, sign the MCODE value itself
const hash = crypto.createHash("sha256").update(MCODE).digest("hex");
const sign = crypto.sign("sha256", Buffer.from(hash), privateKey);
const signB64 = sign.toString("base64");
const res = await fetch("https://api.onepay.vip/api/v1/query/balance", {
headers: { "ONEPAY-SIGN": signB64, "ONEPAY-MCODE": MCODE }
});
return res.json();
// { code: 0, amount: 50000000, settleAmount: 10000000, freezeAmount: 0 }
};
// For balance query, sign the MCODE value itself
hash := sha256.Sum256([]byte(mcode))
hashHex := hex.EncodeToString(hash[:])
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256,
sha256.Sum256([]byte(hashHex))[:])
signB64 := base64.StdEncoding.EncodeToString(sig)
req, _ := http.NewRequest("GET", "https://api.onepay.vip/api/v1/query/balance", nil)
req.Header.Set("ONEPAY-SIGN", signB64)
req.Header.Set("ONEPAY-MCODE", mcode)
def query_balance():
# For balance query, sign the MCODE value itself
hash_hex = hashlib.sha256(MCODE.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
signature = pkcs1_15.new(key).sign(SHA256.new(hash_hex.encode()))
sign_b64 = base64.b64encode(signature).decode()
resp = requests.get("https://api.onepay.vip/api/v1/query/balance",
headers={"ONEPAY-SIGN": sign_b64, "ONEPAY-MCODE": MCODE})
return resp.json()
# {"code": 0, "amount": 50000000, "settleAmount": 10000000, "freezeAmount": 0}
// For balance query, sign the MCODE value itself
String hash = sha256Hex(MCODE);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(hash.getBytes());
String signB64 = Base64.getEncoder().encodeToString(sig.sign());
HttpURLConnection conn = (HttpURLConnection)
new URL("https://api.onepay.vip/api/v1/query/balance").openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("ONEPAY-SIGN", signB64);
conn.setRequestProperty("ONEPAY-MCODE", MCODE);
// For balance query, sign the MCODE value itself
$hashHex = hash("sha256", $mcode);
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_sign($hashHex, $signature, $privKey, OPENSSL_ALGO_SHA256);
$signB64 = base64_encode($signature);
$ch = curl_init("https://api.onepay.vip/api/v1/query/balance");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"ONEPAY-SIGN: " . $signB64,
"ONEPAY-MCODE: " . $mcode
],
CURLOPT_RETURNTRANSFER => true
]);
$result = json_decode(curl_exec($ch), true);
// {"code": 0, "amount": 50000000, "settleAmount": 10000000, "freezeAmount": 0}
{
"code": 0,
"amount": 50000000,
"settleAmount": 10000000,
"freezeAmount": 0
}
Webhooks
OnePay sends asynchronous notifications to your notifyUrl when order status changes. You must respond with the string SUCCESS, otherwise OnePay will retry up to 5 times.
ONEPAY-SIGN header. The decrypted value should match the SHA-256 hash of the sorted callback body parameters. Ensure idempotent handling — the same notification may arrive multiple times.
Payin Callback
Sent when a payin order is paid or settled. Non-realtime settlement orders may trigger two separate callbacks.
| Field | Type | Description |
|---|---|---|
amount | integer | Order amount (cents) |
merchantOid | string | Your merchant order ID |
orderOid | string | OnePay system order ID |
timestamp | integer | Unix timestamp |
status | string | Order status (see below) |
settleStatus | string | Settlement status (see below) |
order_success | Payment received |
order_fail | Payment failed |
order_await | In progress |
settle_success | Settled |
settle_await | Pending settlement |
settle_unknown | N/A (order not successful) |
{
"amount": 1000000,
"merchantOid": "cf75c3a8-6028-4a62-9722-2cd5bc4b820c",
"orderOid": "CLN250325iFgO5Ibt",
"timestamp": 1742895347,
"status": "order_success",
"settleStatus": "settle_success"
}
# Verification steps:
# 1. Receive POST with headers ONEPAY-SIGN and ONEPAY-MCODE
# 2. Sort body params by key → concatenate as key=value&key=value
# 3. SHA-256 hash the sorted string
# 4. Decrypt ONEPAY-SIGN with your RSA private key (PKCS1v15)
# 5. Compare decrypted value with your hash
# 6. If match → process order → respond with "SUCCESS"
# 7. If mismatch → reject the callback
// Express.js handler
app.post("/callback/payin", async (req, res) => {
const sign = req.headers["onepay-sign"];
const body = req.body;
// Sort params, hash, verify
const sorted = Object.keys(body).sort()
.filter(k => body[k] !== "")
.map(k => `${k}=${body[k]}`).join("&");
const hash = crypto.createHash("sha256").update(sorted).digest("hex");
// Decrypt signature with private key
const decrypted = crypto.privateDecrypt(
{ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(sign, "base64")
).toString();
if (decrypted !== hash) return res.status(400).send("FAIL");
// Process order (idempotent!)
if (body.status === "order_success") {
await updateOrderStatus(body.merchantOid, body.status, body.settleStatus);
}
res.send("SUCCESS");
});
func PayinCallback(c *gin.Context) {
sign := c.GetHeader("ONEPAY-SIGN")
var body map[string]interface{}
c.ShouldBindJSON(&body)
// Sort params, hash
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
// Decrypt ONEPAY-SIGN with private key
decrypted, err := DecryptWithPrivateKey(privKey, sign)
if err != nil || decrypted != hashHex {
c.String(400, "FAIL")
return
}
// Process order (idempotent)
status := body["status"].(string)
if status == "order_success" {
updateOrderStatus(body["merchantOid"].(string), status)
}
c.String(200, "SUCCESS")
}
# Flask handler
@app.route("/callback/payin", methods=["POST"])
def payin_callback():
sign = request.headers.get("ONEPAY-SIGN")
body = request.get_json()
# Sort params, hash
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if str(v))
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
# Decrypt signature with private key
key = RSA.import_key(open("private.pem").read())
cipher = PKCS1_v1_5.new(key)
decrypted = cipher.decrypt(base64.b64decode(sign), None).decode()
if decrypted != hash_hex:
return "FAIL", 400
# Process order (idempotent)
if body["status"] == "order_success":
update_order(body["merchantOid"], body["status"], body["settleStatus"])
return "SUCCESS"
// Spring Boot handler
@PostMapping("/callback/payin")
public String payinCallback(
@RequestHeader("ONEPAY-SIGN") String sign,
@RequestBody Map<String, Object> body) {
// Sort params, hash
String sorted = new TreeMap<>(body).entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
// Decrypt signature with private key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
String decrypted = new String(cipher.doFinal(Base64.getDecoder().decode(sign)));
if (!decrypted.equals(hash)) return "FAIL";
// Process order (idempotent)
if ("order_success".equals(body.get("status"))) {
updateOrder(body.get("merchantOid"), body.get("status"));
}
return "SUCCESS";
}
// Laravel/raw PHP callback handler
$sign = $_SERVER['HTTP_ONEPAY_SIGN'] ?? '';
$body = json_decode(file_get_contents('php://input'), true);
// Sort params, hash
ksort($body);
$pairs = [];
foreach ($body as $k => $v) {
if ((string)$v !== '') $pairs[] = "$k=$v";
}
$sorted = implode('&', $pairs);
$hashHex = hash('sha256', $sorted);
// Decrypt signature with private key
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_private_decrypt(base64_decode($sign), $decrypted, $privKey);
if ($decrypted !== $hashHex) {
http_response_code(400);
echo "FAIL"; exit;
}
// Process order (idempotent)
if ($body['status'] === 'order_success') {
updateOrder($body['merchantOid'], $body['status'], $body['settleStatus']);
}
echo "SUCCESS";
Payout Callback
Sent when a payout order status changes.
| Field | Type | Description |
|---|---|---|
amount | integer | Order amount (cents) |
merchantOid | string | Your merchant order ID |
orderOid | string | OnePay system order ID |
timestamp | integer | Unix timestamp |
status | string | order_success / order_fail / order_await |
{
"amount": 1500000,
"merchantOid": "6a6ab062-9a35-4a07-87cd-5ca0492c338c",
"orderOid": "PAY250325DoNhh8AY",
"timestamp": 1742899401,
"status": "order_success"
}
# Same verification flow as payin callback:
# 1. Sort body params by key → key=value&key=value
# 2. SHA-256 hash the sorted string
# 3. Decrypt ONEPAY-SIGN with RSA private key
# 4. Compare → if match, process and respond "SUCCESS"
# Note: payout callback has no settleStatus field
app.post("/callback/payout", async (req, res) => {
const sign = req.headers["onepay-sign"];
const body = req.body;
const sorted = Object.keys(body).sort()
.filter(k => body[k] !== "")
.map(k => `${k}=${body[k]}`).join("&");
const hash = crypto.createHash("sha256").update(sorted).digest("hex");
const decrypted = crypto.privateDecrypt(
{ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(sign, "base64")
).toString();
if (decrypted !== hash) return res.status(400).send("FAIL");
if (body.status === "order_success") {
await completeWithdrawal(body.merchantOid, body.amount);
} else if (body.status === "order_fail") {
await refundWithdrawal(body.merchantOid);
}
res.send("SUCCESS");
});
func PayoutCallback(c *gin.Context) {
sign := c.GetHeader("ONEPAY-SIGN")
var body map[string]interface{}
c.ShouldBindJSON(&body)
sorted := JsonToSortedString(body)
hash := sha256.Sum256([]byte(sorted))
hashHex := hex.EncodeToString(hash[:])
decrypted, err := DecryptWithPrivateKey(privKey, sign)
if err != nil || decrypted != hashHex {
c.String(400, "FAIL")
return
}
status := body["status"].(string)
switch status {
case "order_success":
completeWithdrawal(body["merchantOid"].(string))
case "order_fail":
refundWithdrawal(body["merchantOid"].(string))
}
c.String(200, "SUCCESS")
}
@app.route("/callback/payout", methods=["POST"])
def payout_callback():
sign = request.headers.get("ONEPAY-SIGN")
body = request.get_json()
sorted_str = "&".join(f"{k}={v}" for k, v in sorted(body.items()) if str(v))
hash_hex = hashlib.sha256(sorted_str.encode()).hexdigest()
key = RSA.import_key(open("private.pem").read())
cipher = PKCS1_v1_5.new(key)
decrypted = cipher.decrypt(base64.b64decode(sign), None).decode()
if decrypted != hash_hex:
return "FAIL", 400
if body["status"] == "order_success":
complete_withdrawal(body["merchantOid"])
elif body["status"] == "order_fail":
refund_withdrawal(body["merchantOid"])
return "SUCCESS"
@PostMapping("/callback/payout")
public String payoutCallback(
@RequestHeader("ONEPAY-SIGN") String sign,
@RequestBody Map<String, Object> body) {
String sorted = new TreeMap<>(body).entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
String hash = sha256Hex(sorted);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
String decrypted = new String(cipher.doFinal(Base64.getDecoder().decode(sign)));
if (!decrypted.equals(hash)) return "FAIL";
switch (body.get("status").toString()) {
case "order_success": completeWithdrawal(body); break;
case "order_fail": refundWithdrawal(body); break;
}
return "SUCCESS";
}
$sign = $_SERVER['HTTP_ONEPAY_SIGN'] ?? '';
$body = json_decode(file_get_contents('php://input'), true);
ksort($body);
$pairs = [];
foreach ($body as $k => $v) {
if ((string)$v !== '') $pairs[] = "$k=$v";
}
$sorted = implode('&', $pairs);
$hashHex = hash('sha256', $sorted);
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_private_decrypt(base64_decode($sign), $decrypted, $privKey);
if ($decrypted !== $hashHex) {
http_response_code(400);
echo "FAIL"; exit;
}
switch ($body['status']) {
case 'order_success': completeWithdrawal($body); break;
case 'order_fail': refundWithdrawal($body); break;
}
echo "SUCCESS";
Signature Utilities
Complete signing and verification implementations. Copy these utility functions into your project — no SDK installation required.
package onepay
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"sort"
"strings"
)
// JsonToSortedString converts a JSON string to a sorted key=value&key=value string
func JsonToSortedString(jsonStr string) (string, error) {
var data map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
return "", err
}
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
pairs := make([]string, 0, len(keys))
for _, k := range keys {
var v string
switch val := data[k].(type) {
case string:
v = val
case float64:
v = fmt.Sprintf("%v", int64(val))
default:
v = fmt.Sprintf("%v", val)
}
if v != "" {
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
}
}
return strings.Join(pairs, "&"), nil
}
// StringToSHA256 returns the hex-encoded SHA-256 hash
func StringToSHA256(s string) string {
hash := sha256.Sum256([]byte(s))
return hex.EncodeToString(hash[:])
}
// ImportPrivateKey imports an RSA private key from PEM (PKCS#1 or PKCS#8)
func ImportPrivateKey(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse private key PEM")
}
switch block.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(block.Bytes)
case "PRIVATE KEY":
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil { return nil, err }
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok { return nil, errors.New("not an RSA private key") }
return rsaKey, nil
default:
return nil, errors.New("unknown private key format")
}
}
// SignWithPrivateKey signs a message with RSA PKCS1v15 and returns Base64
func SignWithPrivateKey(privateKey *rsa.PrivateKey, message string) (string, error) {
hashed := sha256.Sum256([]byte(message))
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:])
if err != nil { return "", err }
return base64.StdEncoding.EncodeToString(signature), nil
}
// DecryptWithPrivateKey decrypts Base64-encoded ciphertext with RSA PKCS1v15
func DecryptWithPrivateKey(privateKey *rsa.PrivateKey, ciphertextBase64 string) (string, error) {
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64)
if err != nil { return "", err }
plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext)
if err != nil { return "", err }
return string(plaintext), nil
}
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;
import javax.crypto.Cipher;
public class OnePay {
/** Sort JSON params → key=value&key=value */
public static String jsonToSortedString(Map<String, Object> data) {
return new TreeMap<>(data).entrySet().stream()
.filter(e -> !e.getValue().toString().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
}
/** SHA-256 hex digest */
public static String stringToSHA256(String input) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes());
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
String h = Integer.toHexString(0xff & b);
if (h.length() == 1) hex.append('0');
hex.append(h);
}
return hex.toString();
}
/** Import RSA private key from PKCS#8 PEM */
public static PrivateKey importPrivateKey(String privPEM) throws Exception {
String cleaned = privPEM
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] encoded = Base64.getDecoder().decode(cleaned);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
return KeyFactory.getInstance("RSA").generatePrivate(spec);
}
/** RSA PKCS1v15 sign → Base64 */
public static String signWithPrivateKey(PrivateKey key, String message) throws Exception {
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(key);
sig.update(message.getBytes());
return Base64.getEncoder().encodeToString(sig.sign());
}
/** RSA PKCS1v15 decrypt → plaintext */
public static String decryptWithPrivateKey(PrivateKey key, String ciphertextB64) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(ciphertextB64));
return new String(decrypted);
}
/** Complete: sort → SHA256 → RSA sign → Base64 */
public static String generateSign(Map<String, Object> body, String privPEM) throws Exception {
String sorted = jsonToSortedString(body);
String hash = stringToSHA256(sorted);
PrivateKey pk = importPrivateKey(privPEM);
return signWithPrivateKey(pk, hash);
}
/** Verify callback: sort → SHA256 → decrypt sign → compare */
public static boolean verifyCallback(Map<String, Object> body, String signHeader, String privPEM) throws Exception {
String sorted = jsonToSortedString(body);
String hash = stringToSHA256(sorted);
PrivateKey pk = importPrivateKey(privPEM);
String decrypted = decryptWithPrivateKey(pk, signHeader);
return hash.equals(decrypted);
}
}
<?php
class OnePay {
/** Sort params → key=value&key=value */
public static function jsonToSortedString($data) {
ksort($data);
$pairs = [];
foreach ($data as $k => $v) {
if ((string)$v !== '') $pairs[] = "$k=$v";
}
return implode('&', $pairs);
}
/** SHA-256 hex digest */
public static function stringToSHA256($str) {
return hash('sha256', $str);
}
/** RSA PKCS1v15 sign → Base64 */
public static function signWithPrivateKey($privateKeyPem, $message) {
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_sign($message, $signature, $privKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
/** RSA PKCS1v15 decrypt → plaintext */
public static function decryptWithPrivateKey($privateKeyPem, $ciphertextB64) {
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_private_decrypt(base64_decode($ciphertextB64), $plaintext, $privKey);
return $plaintext;
}
/** Complete: sort → SHA256 → RSA sign → Base64 */
public static function sign($body, $privateKeyPem) {
$sorted = self::jsonToSortedString($body);
$hash = self::stringToSHA256($sorted);
return self::signWithPrivateKey($privateKeyPem, $hash);
}
/** Verify callback: sort → SHA256 → decrypt sign → compare */
public static function verifyCallback($body, $signHeader, $privateKeyPem) {
$sorted = self::jsonToSortedString($body);
$hash = self::stringToSHA256($sorted);
$decrypted = self::decryptWithPrivateKey($privateKeyPem, $signHeader);
return $hash === $decrypted;
}
}
import hashlib, base64, json
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.Cipher import PKCS1_v1_5
def json_to_sorted_string(data: dict) -> str:
"""Sort params by key, join as key=value&key=value"""
return "&".join(
f"{k}={v}" for k, v in sorted(data.items())
if str(v) != ""
)
def string_to_sha256(s: str) -> str:
"""SHA-256 hex digest"""
return hashlib.sha256(s.encode()).hexdigest()
def sign_with_private_key(private_key_pem: str, message: str) -> str:
"""RSA PKCS1v15 sign -> Base64"""
key = RSA.import_key(private_key_pem)
h = SHA256.new(message.encode())
signature = pkcs1_15.new(key).sign(h)
return base64.b64encode(signature).decode()
def decrypt_with_private_key(private_key_pem: str, ciphertext_b64: str) -> str:
"""RSA PKCS1v15 decrypt -> plaintext"""
key = RSA.import_key(private_key_pem)
cipher = PKCS1_v1_5.new(key)
ciphertext = base64.b64decode(ciphertext_b64)
return cipher.decrypt(ciphertext, None).decode()
def generate_sign(body: dict, private_key_pem: str) -> str:
"""Complete: sort -> SHA256 -> RSA sign -> Base64"""
sorted_str = json_to_sorted_string(body)
hash_hex = string_to_sha256(sorted_str)
return sign_with_private_key(private_key_pem, hash_hex)
def verify_callback(body: dict, sign_header: str, private_key_pem: str) -> bool:
"""Verify callback: sort -> SHA256 -> decrypt sign -> compare"""
sorted_str = json_to_sorted_string(body)
hash_hex = string_to_sha256(sorted_str)
decrypted = decrypt_with_private_key(private_key_pem, sign_header)
return hash_hex == decrypted
import crypto from 'crypto';
function jsonToSortedString(data) {
return Object.keys(data).sort()
.filter(k => String(data[k]) !== "")
.map(k => `${k}=${data[k]}`)
.join("&");
}
function stringToSHA256(str) {
return crypto.createHash("sha256").update(str).digest("hex");
}
function signWithPrivateKey(privateKeyPem, message) {
const sign = crypto.createSign("SHA256");
sign.update(message);
return sign.sign(privateKeyPem, "base64");
}
function decryptWithPrivateKey(privateKeyPem, ciphertextB64) {
return crypto.privateDecrypt(
{ key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(ciphertextB64, "base64")
).toString();
}
function generateSign(body, privateKeyPem) {
const sorted = jsonToSortedString(body);
const hashHex = stringToSHA256(sorted);
return signWithPrivateKey(privateKeyPem, hashHex);
}
function verifyCallback(body, signHeader, privateKeyPem) {
const sorted = jsonToSortedString(body);
const hashHex = stringToSHA256(sorted);
const decrypted = decryptWithPrivateKey(privateKeyPem, signHeader);
return hashHex === decrypted;
}
Error Codes
All API responses include a code field. 0 indicates success.
| Code | Name | Description |
|---|---|---|
0 | Success | Request completed successfully |
7 | Failed | General failure |
101 | Merchant Not Found | Invalid or inactive merchant code |
102 | Insufficient Balance | Merchant balance too low for payout |
103 | Signature Error | RSA signature verification failed |
404 | Not Found | Resource not found |
4001 | Invalid Params | One or more parameters are invalid |
4002 | Missing Params | Required parameter is missing |
4003 | Invalid Format | Parameter format is incorrect |
5001 | Network Error | Upstream network issue |
5002 | Request Timeout | Upstream request timed out |
5003 | Service Unavailable | Service temporarily down |
9001 | Header Error | Missing or invalid request headers |
10001 | Order Error | General order processing error |
10002 | Order Not Found | Order does not exist |
10003 | Order Status Error | Invalid order status transition |
10004 | Order Closed | Order has been finalized |
Bank Codes
Supported banks and e-wallets for payout disbursements. Use the bankCode value in your payout request.
| Code | Short Name | Full Name |
|---|---|---|
014 | BCA | Bank Central Asia (BCA) |
009 | BNI | Bank Negara Indonesia (BNI) |
002 | BRI | Bank Rakyat Indonesia (BRI) |
008 | MANDIRI | Bank Mandiri |
4510 | BSI | Bank Syariah Indonesia (BSI) |
022 | CIMB | Bank CIMB Niaga |
013 | PERMATA | Bank Permata |
011 | DANAMON | Bank Danamon |
200 | BTN | Bank Tabungan Negara (BTN) |
019 | PANIN | Bank Panin |
016 | MAYBANK | Bank Maybank |
028 | OCBC | Bank OCBC NISP |
023 | UOB | TMRW/Bank UOB Indonesia |
426 | MEGA | Bank Mega |
213 | BTPN | Bank BTPN |
153 | SINARMAS | Bank Sinarmas |
046 | DBS | Bank DBS Indonesia |
484 | HANA | LINE Bank/KEB Hana |
542 | JAGO | Bank ARTOS/Bank Jago |
535 | SEABANK | Seabank/Bank Kesejahteraan Ekonomi |
| E-Wallets | ||
10001 | OVO | OVO |
10002 | DANA | DANA |
10003 | GOPAY | GOPAY |
10008 | SHOPEEPAY | SHOPEEPAY |
10009 | LINKAJA | LINKAJA |
Showing popular banks. Full list includes 150+ institutions. Contact support for the complete bank code table.
Changelog
- Added e-wallet support (OVO, DANA, GOPAY, ShopeePay, LinkAja)
- Improved callback retry mechanism
- Added settlement status tracking in payin callbacks
- Introduced
feeTypeparameter for payout orders - Balance query now returns
settleAmountandfreezeAmount
- Initial release with payin, payout, order query, and balance query
- RSA-2048 signature authentication
- Support for 140+ Indonesian banks