Trusted Pay Merchant API
Accept QRIS payments and send withdrawals to bank accounts and e-wallets with one server-to-server JSON API.
Introduction
Everything runs over HTTPS with JSON bodies, signed with your client secret. Your server calls Trusted Pay; Trusted Pay calls your server back when a payment or withdrawal reaches its final status.
| Environment | Base URL |
|---|---|
| Production | https://api.trustedpay.vip |
| Sandbox | https://staging.trustedpay.vip |
The base URL only tells sandbox and production apart. It is permanent: it never changes, whatever happens behind the API.
What you receive from Trusted Pay
- Client ID: identifies your account in every request.
- Client secret: signs requests and verifies callbacks. Keep it on your server only.
- Merchant portal login: transactions, balances, settlements and callback logs.
What you send to Trusted Pay
- Server IP addresses that will call the API (IP whitelist).
- Default callback URL (HTTPS, publicly reachable).
Do not put it in a browser, mobile app or public repository. Anyone with the secret and a whitelisted IP can create withdrawals. If it may have leaked, ask Trusted Pay to issue a new one; the old secret stops working immediately.
Quick start
- Get credentials. Receive your Client ID and client secret, and have your server IPs whitelisted.
- Create a deposit. Call
/api/order/createand renderqrStringas a QR code for your customer. - Receive the callback. When the customer pays, Trusted Pay posts a signed
PAIDcallback to yournotifyUrl. Verify the signature, then credit your customer. - Withdraw. Check
/api/withdrawal/balance, then call/api/withdrawal/create. Wait for theSUCCESSorFAILEDcallback. - Reconcile. If a callback is late or a request timed out, use the query endpoints with the same
orderId.
Requests & responses
- Every endpoint is
POSTwithContent-Type: application/json. Thesignatureis a field in the JSON body; there are no auth headers. - Amounts in requests are whole numbers in IDR (no decimals). Amounts in withdrawal responses are strings with two decimals, e.g.
"50000.00". - Times are
YYYY-MM-DD HH:mm:ssin GMT+7. orderIdis your own reference and must be unique per merchant for deposits, and separately for withdrawals. Use letters, digits,-and_, up to 100 characters.- Every response has HTTP status 200 with a
codefield. Readcode, not the HTTP status.
{
"code": 200,
"message": "Success",
"data": { /* endpoint-specific object, null on errors */ }
}Treat the outcome as unknown. Do not create a new transaction with a different orderId. Call the query endpoint with the same orderId, or repeat the same create request: it is idempotent.
Signatures
Join the fields below with a pipe |, append your client secret, hash with SHA-512 and send the lowercase hex digest as signature. Use the exact values you put in the body; amounts are plain integers.
| Endpoint | Signature string |
|---|---|
/api/order/create | clientId|orderId|totalAmount|clientSecret |
/api/order/query | clientId|orderId|clientSecret |
/api/withdrawal/create | clientId|orderId|requestAmount|clientSecret |
/api/withdrawal/query | clientId|orderId|clientSecret |
/api/withdrawal/balance | clientId|method|clientSecret |
/api/withdrawal/balance-detail | clientId|clientSecret |
| Callbacks (from Trusted Pay) | clientId|orderId|refCode|status|clientSecret |
Worked example
| clientId | 8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F |
| orderId / totalAmount | INV-1001 / 200000 |
| clientSecret | your-client-secret |
| String to hash | 8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F|INV-1001|200000|your-client-secret |
| signature | 0d80561a60fb1bdb3997158f84fb8d808ea2dc1cbe7357aeacb647840ea37abc0097547132d28a01d48a7d46658a4772e281a6608590ac264003b4d448e5b250 |
import hashlib def sign(*parts) -> str: """SHA-512 of the parts joined with '|' (client secret last), lowercase hex.""" return hashlib.sha512("|".join(str(p) for p in parts).encode("utf-8")).hexdigest() signature = sign(client_id, order_id, total_amount, client_secret)
function sign(...$parts): string { // SHA-512 of the parts joined with '|' (client secret last), lowercase hex. return hash('sha512', implode('|', $parts)); } $signature = sign($clientId, $orderId, $totalAmount, $clientSecret);
import { createHash } from "node:crypto"; // SHA-512 of the parts joined with '|' (client secret last), lowercase hex. const sign = (...parts) => createHash("sha512").update(parts.join("|"), "utf8").digest("hex"); const signature = sign(clientId, orderId, totalAmount, clientSecret);
import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.HexFormat; static String sign(String... parts) throws Exception { byte[] digest = MessageDigest.getInstance("SHA-512") .digest(String.join("|", parts).getBytes(StandardCharsets.UTF_8)); return HexFormat.of().formatHex(digest); // lowercase hex } String signature = sign(clientId, orderId, String.valueOf(totalAmount), clientSecret);
import ( "crypto/sha512" "encoding/hex" "strconv" "strings" ) func sign(parts ...string) string { sum := sha512.Sum512([]byte(strings.Join(parts, "|"))) return hex.EncodeToString(sum[:]) // lowercase hex } signature := sign(clientID, orderID, strconv.Itoa(totalAmount), clientSecret)
Errors & messages
code | Meaning | What to do |
|---|---|---|
200 | Success | Use data. |
400 | Validation or business error; message says why | Fix the request. Do not retry blindly. |
403 | IP address not allowed. | Call from a whitelisted server IP, or ask Trusted Pay to add the IP. |
429 | Too many requests | Slow down and retry after a second (see rate limits). |
Common messages
| Message | Cause |
|---|---|
Invalid clientId. | Unknown or deactivated Client ID. |
Invalid signature. | Wrong signature string, field order, amount format or secret. |
Duplicate orderId. | The orderId was already used with different details (or for an expired order). |
Order is being processed. Please query the order status. | The same deposit is still being created. Query it in a few seconds. |
Withdrawal is being processed. Please query the status. | The same withdrawal request is already in progress. Query it. |
Payment gateway temporarily unavailable. Please try again. | The QR could not be issued right now. Retry the same request. |
Payment channel is not configured. | Your account is not activated for this product yet. Contact Trusted Pay. |
Minimum amount is … / Maximum amount is … | Amount outside your account limits; the message states the limit. |
Insufficient balance. | Available balance is lower than request amount + fee. Nothing was debited. |
Account number could not be verified. | The beneficiary account does not exist or is inactive. Nothing was debited. |
Bank is not available for this payout. / E-wallet is not available for this payout. | Unsupported bankCode / ewalletCode (see codes). |
Withdrawal was rejected. | The withdrawal was declined. Nothing was debited. |
field: message | A field failed validation, e.g. customerPhone: Phone number must start with 62. |
API messages are always in English, whatever the language of this page.
Limits & rate limits
| Product | Default per transaction |
|---|---|
| Deposit (QRIS) | Rp 10.000 – Rp 10.000.000 |
| Withdrawal | Rp 10.000 – Rp 50.000.000 (request amount, fee added on top) |
Your account can have different limits. The error message always states the limit that applied, and /api/withdrawal/balance returns the largest withdrawal you can make now.
| Endpoint | Default rate limit |
|---|---|
/api/order/create | 10 requests / second |
/api/order/query | 5 requests / second |
/api/withdrawal/create | 5 requests / second |
/api/withdrawal/query, /balance, /balance-detail | 2 requests / second (shared) |
Status changes are pushed to your notifyUrl. Use the query endpoints for reconciliation and after timeouts, not in tight loops.
Deposit (QRIS)
POST/api/order/create
Creates a QRIS payment and returns the QR content to show your customer.
| Field | Type | Description |
|---|---|---|
clientId required | string | Your Client ID. |
orderId required | string ≤ 100 | Your unique deposit reference. |
totalAmount required | integer | Amount in IDR the customer pays. |
paymentType optional | string | QRIS (default and only value). |
customerName required | string ≤ 255 | Payer name. |
customerEmail required | string | Valid email address. |
customerPhone required | string | Starts with 62, digits only, 10–15 characters, e.g. 6281234567890. |
notifyUrl optional | string ≤ 500 | Callback URL for this order. Defaults to your account's callback URL. Must be a public http(s) URL. |
returnUrl optional | string | Where you send the customer afterwards (stored, not used by Trusted Pay). |
signature required | string | SHA512(clientId|orderId|totalAmount|clientSecret) |
curl -X POST https://api.trustedpay.vip/api/order/create \ -H "Content-Type: application/json" \ -d '{ "clientId": "8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F", "orderId": "INV-1001", "totalAmount": 200000, "paymentType": "QRIS", "customerName": "Andi", "customerEmail": "[email protected]", "customerPhone": "6281234567890", "notifyUrl": "https://merchant.example/callback/trustedpay", "signature": "0d80561a60fb1bdb3997158f84fb8d808ea2dc1cbe7357aeacb647840ea37abc0097547132d28a01d48a7d46658a4772e281a6608590ac264003b4d448e5b250" }'
import hashlib, requests BASE_URL = "https://api.trustedpay.vip" CLIENT_ID, CLIENT_SECRET = "8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F", "your-client-secret" def sign(*parts): return hashlib.sha512("|".join(map(str, parts)).encode()).hexdigest() body = { "clientId": CLIENT_ID, "orderId": "INV-1001", "totalAmount": 200000, "paymentType": "QRIS", "customerName": "Andi", "customerEmail": "[email protected]", "customerPhone": "6281234567890", } body["signature"] = sign(CLIENT_ID, body["orderId"], body["totalAmount"], CLIENT_SECRET) res = requests.post(f"{BASE_URL}/api/order/create", json=body, timeout=40).json() if res["code"] == 200: qr_string = res["data"]["qrString"] # render as a QR code
$baseUrl = 'https://api.trustedpay.vip'; $clientId = '8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F'; $clientSecret = 'your-client-secret'; $body = [ 'clientId' => $clientId, 'orderId' => 'INV-1001', 'totalAmount' => 200000, 'paymentType' => 'QRIS', 'customerName' => 'Andi', 'customerEmail' => '[email protected]', 'customerPhone' => '6281234567890', ]; $body['signature'] = hash('sha512', implode('|', [$clientId, $body['orderId'], $body['totalAmount'], $clientSecret])); $ch = curl_init("$baseUrl/api/order/create"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 40, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($body), ]); $res = json_decode(curl_exec($ch), true);
import { createHash } from "node:crypto"; const BASE_URL = "https://api.trustedpay.vip"; const CLIENT_ID = "8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F", CLIENT_SECRET = "your-client-secret"; const sign = (...p) => createHash("sha512").update(p.join("|")).digest("hex"); const body = { clientId: CLIENT_ID, orderId: "INV-1001", totalAmount: 200000, paymentType: "QRIS", customerName: "Andi", customerEmail: "[email protected]", customerPhone: "6281234567890", }; body.signature = sign(CLIENT_ID, body.orderId, body.totalAmount, CLIENT_SECRET); const res = await (await fetch(`${BASE_URL}/api/order/create`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), })).json();
{
"code": 200,
"message": "Success",
"data": {
"refCode": "PX261021003514CR1RE6",
"transactionTime": "2026-10-21 00:35:14",
"orderId": "INV-1001",
"totalAmount": 200000,
"customerName": "Andi",
"customerEmail": "[email protected]",
"customerPhone": "6281234567890",
"notifyUrl": "https://merchant.example/callback/trustedpay",
"returnUrl": null,
"status": "PENDING",
"paidAt": null,
"expiredAt": "2026-10-21 01:05:14",
"qrString": "00020101021226610012ID.CO.QRIS.WWW…6304B896"
}
}- Render
qrStringas a QR code (any QR library). Any QRIS-enabled banking or e-wallet app can pay it. refCodeis Trusted Pay's reference. Store it next to yourorderId.- The QR is valid for 30 minutes by default;
expiredAtgives the exact time it stops being payable.
POST/api/order/query
| Field | Type | Description |
|---|---|---|
clientId required | string | Your Client ID. |
orderId required | string | Your deposit reference. |
signature required | string | SHA512(clientId|orderId|clientSecret) |
Returns the same data object as create, with the current status and paidAt. Unknown orders return code: 400, Order not found.
Order lifecycle
| Status | Meaning | Callback |
|---|---|---|
PENDING | QR issued, waiting for the customer. | – |
PAID | Payment confirmed. The net amount is credited to your pending balance. | Yes |
EXPIRED | Not paid in time. Set once the payment network confirms the QR was not paid, usually within 10 minutes after expiredAt. | No |
FAILED | No QR could be issued. You may call create again with the same orderId; a new refCode and QR are issued. | No |
If the customer paid at the last moment and the network reports it late, the order changes from EXPIRED to PAID. Your balance is credited and you receive the normal PAID callback. Always honour a valid PAID callback, even for an order you already treated as expired.
- Repeating create with the same
orderIdandtotalAmountreturns the existing order (same QR while it isPENDING). - The same
orderIdwith a differenttotalAmount, or for anEXPIREDorder, returnsDuplicate orderId.. Use a neworderIdfor a new payment.
Withdrawal
POST/api/withdrawal/create
Sends money from your available balance to a bank account or e-wallet. The beneficiary account is verified before any money moves.
| Field | Type | Description |
|---|---|---|
clientId required | string | Your Client ID. |
orderId required | string ≤ 100 | Your unique withdrawal reference. |
requestAmount required | integer | Amount in IDR the beneficiary receives. The withdrawal fee is added on top. |
method required | string | BANK_ACCOUNT or EWALLET. |
bankCode | string | 3-digit bank code, required for BANK_ACCOUNT (see codes). |
ewalletCode | string | Required for EWALLET: DANA, GOPAY, OVO, SHOPEEPAY, LINKAJA, SAKUKU. |
accountNumber required | string ≤ 50 | Digits only. For e-wallets, the registered phone number. |
accountName required | string ≤ 255 | Beneficiary name as you know it. The verified name is returned in the response. |
notifyUrl optional | string ≤ 500 | Callback URL for this withdrawal. Defaults to your account's callback URL. |
signature required | string | SHA512(clientId|orderId|requestAmount|clientSecret) |
curl -X POST https://api.trustedpay.vip/api/withdrawal/create \ -H "Content-Type: application/json" \ -d '{ "clientId": "8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F", "orderId": "WD-1001", "requestAmount": 50000, "method": "BANK_ACCOUNT", "bankCode": "014", "accountNumber": "1234567890", "accountName": "Andi Wijaya", "signature": "<SHA512(clientId|WD-1001|50000|clientSecret)>" }'
body = {
"clientId": CLIENT_ID, "orderId": "WD-1001", "requestAmount": 50000,
"method": "EWALLET", "ewalletCode": "DANA",
"accountNumber": "081234567890", "accountName": "Andi Wijaya",
}
body["signature"] = sign(CLIENT_ID, body["orderId"], body["requestAmount"], CLIENT_SECRET)
res = requests.post(f"{BASE_URL}/api/withdrawal/create", json=body, timeout=90).json()
{
"code": 200,
"message": "Success",
"data": {
"refCode": "WX261021101502KD8Q2M",
"orderId": "WD-1001",
"accountNumber": "1234567890",
"accountName": "ANDI WIJAYA",
"bank": { "name": "BCA (Bank Central Asia)", "code": "014" },
"requestAmount": "50000.00",
"withdrawalFee": "1600.00",
"withdrawalAmount": "51600.00",
"status": "PENDING",
"statusRemark": null,
"requestAt": "2026-10-21 10:15:02",
"processedAt": null,
"payoutProvider": "TRUSTEDPAY",
"method": "BANK_ACCOUNT"
}
}withdrawalAmount=requestAmount+withdrawalFee, debited from your available balance when the request is accepted.accountNameis the name verified with the bank or e-wallet and may differ from the name you sent.payoutProvideris alwaysTRUSTEDPAY.- Allow up to 90 seconds for the response: the account is verified before the transfer is submitted.
POST/api/withdrawal/query
Body: clientId, orderId, signature = SHA512(clientId|orderId|clientSecret). Returns the same data object as create. Unknown withdrawals return Withdrawal not found.
POST/api/withdrawal/balance
Your balance and the fee for a withdrawal method. Body: clientId, method (BANK_ACCOUNT default, or EWALLET), signature = SHA512(clientId|method|clientSecret).
{
"payoutProvider": "TRUSTEDPAY",
"method": "BANK_ACCOUNT",
"pendingBalance": "34580.00",
"availableBalance": "144000.00",
"withdrawalFee": "1600.00",
"maxRequestAmount": "142400.00",
"hasBalance": true
}maxRequestAmount is the largest requestAmount you can send now (available balance minus fee, capped by your limit). hasBalance is true when it reaches the minimum withdrawal.
POST/api/withdrawal/balance-detail
Body: clientId, signature = SHA512(clientId|clientSecret). Returns { "pendingBalance": "34580.00", "availableBalance": "144000.00" }.
Withdrawal lifecycle
| Status | Meaning | Callback |
|---|---|---|
PENDING | Accepted and debited; being submitted. | – |
ON_PROCESS | Transfer in progress at the bank or e-wallet. | – |
SUCCESS | Money delivered. processedAt is set. | Yes |
FAILED | Not delivered. withdrawalAmount is refunded automatically to your available balance; statusRemark says why. | Yes |
- If the account cannot be verified, create returns
code: 400and nothing is debited. - Repeating create with the same
orderIdand the same amount, method, bank or e-wallet and account returns the existing withdrawal: nothing is debited twice. - The same
orderIdwith any of those different returnsDuplicate orderId..
A timed-out request may still have been accepted. Query the original orderId (or repeat the identical request) and wait for the final callback. Creating a second withdrawal with a new orderId can pay the beneficiary twice.
Pending & available balance
- Paid deposits are credited, after fees, to your pending balance.
- Funds become available once settled. Settlement runs daily: deposits paid on day D (00:00:00–23:59:59 GMT+7) are normally available on D+1 at 15:00 GMT+7, every day of the week.
- Availability follows the actual settlement, so it can occasionally be later.
- Only the available balance can be withdrawn. The merchant portal shows the expected release time of pending funds.
Callbacks
Trusted Pay sends a POST with a JSON body to your notifyUrl:
- When a deposit becomes
PAID. - When a withdrawal becomes
SUCCESSorFAILED.
| Rule | Detail |
|---|---|
| Acknowledge | Return any HTTP 2xx within 5 seconds. Do slow work after responding. |
| Retries | Up to 6 attempts. Waits between attempts: 10 s, 30 s, 1 min, 5 min, 15 min. Redirects are not followed. |
| Duplicates | A callback can arrive more than once. Process it idempotently by refCode + status. |
| Order | Do not rely on arrival order; use status. |
| Security | Always verify the signature. Optionally confirm with a query call. |
| Resend | Callbacks can be resent from the merchant portal (order or withdrawal detail). |
Deposit callback
{
"clientId": "8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F",
"orderId": "INV-1001",
"refCode": "PX261021003514CR1RE6",
"totalAmount": 200000,
"status": "PAID",
"paidAt": "2026-10-21 00:37:07",
"rrn": "AX4UEDYO2VXN",
"signature": "SHA512(clientId|orderId|refCode|status|clientSecret)"
}rrn is the payment network's reference (useful for customer disputes); it can be null.
Withdrawal callback
{
"clientId": "8F3C2A9E1B7D4C6A0E5F9B2D7C1A3E8F",
"refCode": "WX261021101502KD8Q2M",
"orderId": "WD-1001",
"accountNumber": "1234567890",
"accountName": "ANDI WIJAYA",
"bank": { "name": "BCA (Bank Central Asia)", "code": "014" },
"method": "BANK_ACCOUNT",
"requestAmount": "50000.00",
"withdrawalFee": "1600.00",
"withdrawalAmount": "51600.00",
"status": "SUCCESS",
"statusRemark": null,
"requestAt": "2026-10-21 10:15:02",
"processedAt": "2026-10-21 10:15:31",
"signature": "SHA512(clientId|orderId|refCode|status|clientSecret)"
}statusRemark is filled only for FAILED.
Verify a callback
import hashlib, hmac from flask import Flask, request app = Flask(__name__) @app.post("/callback/trustedpay") def trustedpay_callback(): cb = request.get_json(force=True) expected = hashlib.sha512( "|".join([cb["clientId"], cb["orderId"], cb["refCode"], cb["status"], CLIENT_SECRET]).encode() ).hexdigest() if cb["clientId"] != CLIENT_ID or not hmac.compare_digest(expected, cb.get("signature", "").lower()): return {"ok": False}, 401 # idempotent: skip if (refCode, status) was already handled handle(cb) return {"ok": True}, 200
$cb = json_decode(file_get_contents('php://input'), true); $expected = hash('sha512', implode('|', [$cb['clientId'], $cb['orderId'], $cb['refCode'], $cb['status'], $clientSecret])); if ($cb['clientId'] !== $clientId || !hash_equals($expected, strtolower($cb['signature'] ?? ''))) { http_response_code(401); exit; } // idempotent: skip if (refCode, status) was already handled handle($cb); http_response_code(200); echo json_encode(['ok' => true]);
import express from "express"; import { createHash, timingSafeEqual } from "node:crypto"; const app = express(); app.post("/callback/trustedpay", express.json(), (req, res) => { const cb = req.body; const expected = createHash("sha512") .update([cb.clientId, cb.orderId, cb.refCode, cb.status, CLIENT_SECRET].join("|")).digest("hex"); const given = String(cb.signature || "").toLowerCase(); const ok = cb.clientId === CLIENT_ID && given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected)); if (!ok) return res.status(401).json({ ok: false }); handle(cb); // idempotent by refCode + status res.json({ ok: true }); });
Bank & e-wallet codes
Send ewalletCode for EWALLET withdrawals, or the 3-digit bankCode for BANK_ACCOUNT withdrawals. Codes not listed here are rejected before any money moves.
E-wallets
| ewalletCode | E-wallet |
|---|---|
DANA | DANA |
GOPAY | GoPay |
OVO | OVO |
SHOPEEPAY | ShopeePay |
LINKAJA | LinkAja |
SAKUKU | Sakuku |
Banks (128)
| bankCode | Bank |
|---|---|
002 | BRI (Bank Rakyat Indonesia) |
008 | Bank Mandiri |
009 | BNI (Bank Negara Indonesia) |
011 | Bank Danamon |
013 | Bank Permata |
014 | BCA (Bank Central Asia) |
016 | Maybank Indonesia |
019 | Panin Bank |
022 | CIMB Niaga |
023 | UOB Indonesia (TMRW) |
028 | OCBC NISP |
031 | Citibank |
032 | JPMorgan Chase Bank |
033 | Bank of America |
036 | China Construction Bank Indonesia |
037 | Bank Artha Graha Internasional |
042 | MUFG Bank (Bank of Tokyo Mitsubishi UFJ) |
046 | DBS Indonesia |
047 | Bank Resona Perdania |
048 | Bank Mizuho Indonesia |
050 | Standard Chartered Bank |
054 | Bank Capital Indonesia |
057 | Bank BNP Paribas Indonesia |
061 | ANZ Indonesia |
067 | Deutsche Bank AG |
069 | Bank of China (Hong Kong) |
076 | Bank Bumi Arta |
087 | HSBC Indonesia |
088 | Bank Antardaerah |
089 | Rabobank International Indonesia |
095 | J Trust Bank |
097 | Bank Mayapada |
110 | Bank BJB |
111 | Bank DKI |
112 | BPD DIY |
113 | Bank Jateng |
114 | Bank Jatim |
115 | Bank Jambi |
116 | Bank Aceh Syariah |
117 | Bank Sumut |
118 | Bank Nagari (Sumbar) |
119 | Bank Riau Kepri |
120 | Bank Sumsel Babel |
121 | Bank Lampung |
122 | Bank Kalsel |
123 | Bank Kalbar |
124 | Bank Kaltimtara |
125 | Bank Kalteng |
126 | Bank Sulselbar |
127 | Bank SulutGo |
128 | Bank NTB Syariah |
129 | BPD Bali |
130 | Bank NTT |
131 | Bank Maluku Malut |
132 | Bank Papua |
133 | Bank Bengkulu |
134 | Bank Sulteng |
135 | Bank Sultra |
137 | Bank Banten |
145 | Bank Nusantara Parahyangan |
146 | Bank of India Indonesia |
147 | Bank Muamalat |
151 | Bank Mestika Dharma |
152 | Bank Shinhan Indonesia |
153 | Bank Sinarmas |
157 | Bank Maspion Indonesia |
161 | Bank Ganesha |
164 | ICBC Indonesia |
167 | QNB Indonesia |
200 | BTN (Bank Tabungan Negara) |
212 | Bank Woori Saudara |
213 | BTPN / Jenius |
253 | Bank Nano Syariah |
405 | Bank Victoria Syariah |
425 | BJB Syariah |
426 | Bank Mega |
441 | KB Bank (Bukopin / Wokee) |
459 | Krom Bank Indonesia |
472 | Bank Jasa Jakarta |
484 | KEB Hana / LINE Bank |
485 | MNC Bank (Motion) |
490 | Bank Neo Commerce |
494 | BRI Agroniaga |
498 | SBI Indonesia |
501 | blu / BCA Digital |
503 | Bank Nationalnobu |
506 | Bank Mega Syariah |
513 | Bank Ina Perdana |
517 | Panin Dubai Syariah |
520 | Bank Prima Master |
521 | KB Bukopin Syariah |
523 | Bank Sahabat Sampoerna |
526 | Bank Oke Indonesia |
531 | Bank Amar Indonesia |
535 | SeaBank |
536 | BCA Syariah |
542 | Bank Jago |
547 | BTPN Syariah |
548 | Bank Multi Arta Sentosa |
553 | Bank Mayora Indonesia |
555 | Bank Index Selindo |
559 | Bank CNB (Centratama Nasional) |
564 | Bank Mandiri Taspen |
566 | Bank Victoria International |
567 | Allo Bank |
600 | BPR LSB (ATMB LSB) |
688 | BPR KS |
699 | BPR Eka (Bank Eka) |
721 | Bank Permata UUS |
722 | Bank Danamon UUS |
723 | BTN UUS |
724 | Bank DKI UUS |
725 | Bank Jateng UUS |
727 | Bank Kalbar UUS |
728 | Bank Kalsel UUS |
729 | Bank Kaltimtara UUS |
733 | Bank Sulselbar UUS |
734 | Bank Sinarmas UUS |
736 | BPD DIY UUS |
777 | Finnet |
789 | Dompetku |
867 | BPR Eka |
899 | DOKU |
945 | Bank IBK Indonesia |
947 | Bank Aladin Syariah |
949 | CTBC Indonesia |
950 | Commonwealth Bank |
987 | ATMB Plus |
Go-live checklist
- Production server IPs are whitelisted, and the client secret is stored only on the server.
- Signatures are computed from the exact body values, as lowercase SHA-512 hex.
- The callback endpoint is HTTPS and public, verifies signatures, answers 2xx within 5 s, and is idempotent by
refCode+status. PAIDcallbacks are honoured even for orders you treated as expired.- Timeouts and 5xx are resolved with the query endpoints. Withdrawals are never re-created under a new
orderId. - You read the response
codefield, not just the HTTP status. - Your withdrawal flow checks
/api/withdrawal/balancefirst and handlesInsufficient balance.