C ChaosPay Gateway API
GATEWAY API · v1

ChaosPay Gateway API

Terima pembayaran QRIS dengan REST API JSON yang ditandatangani HMAC-SHA256. Bentuk request, respons, dan webhook-nya mengikuti integrasi gateway yang umum dipakai, jadi merchant yang sudah terintegrasi cukup mengganti base URL dan kredensial.

Base URL

Production
https://api.labubu4d.lol

Semua request lewat HTTPS dengan Content-Type: application/json. Nominal berupa bilangan bulat Rupiah (IDR). Waktu dalam Asia/Jakarta (UTC+7).

Kredensial ada di dashboard merchant, menu 2FA & API: API Key dan Secret Key. Secret Key tidak pernah dikirim dalam request; ia hanya dipakai untuk menandatangani request dan memverifikasi callback.

Pindah dari integrasi lain

Yang perlu diganti hanya tiga hal:

  1. Base URL menjadi https://api.labubu4d.lol.
  2. API Key dan Secret Key menjadi milik akun ChaosPay Anda. Header X-API-Key berisi API Key, dan Secret Key dipakai untuk HMAC.
  3. Callback URL diisi di dashboard, menu Callback Settings.

Hal yang perlu diketahui:

  • Nominal pembayaran dibatasi Rp 10.000 sampai Rp 10.000.000 per transaksi (default; bisa berbeda per akun).
  • callback_url pada request create diterima tetapi diabaikan: callback selalu dikirim ke URL yang tersimpan di dashboard.
  • Biaya (fee) ditanggung merchant (fee_borne_by: "merchant"), jadi amount_paid sama dengan gross_amount.
  • Status cancel ada di daftar nilai tetapi tidak pernah dipakai.

Rate Limit

300 request per menit untuk setiap API Key. Jika terlampaui, API menjawab HTTP 429 dengan header Retry-After (detik). Sebaiknya andalkan callback, bukan polling.

Header & Signature

Semua endpoint kecuali status publik dan health check wajib ditandatangani.

HeaderWajibKeterangan
X-API-KeyYaAPI Key Anda (atau API Key sandbox).
X-TimestampYaUnix time dalam detik. Harus dalam rentang 5 menit dari jam server.
X-NoncePOST/PUT/PATCH/DELETEString acak unik (maks. 128 karakter). Tiap nonce hanya diterima satu kali.
X-SignatureYaHMAC-SHA256 dalam hex huruf kecil.
Rumus
GET:
  X-Signature = hex(HMAC-SHA256(apiKey + timestamp + body, secretKey))

POST / PUT / PATCH / DELETE:
  X-Signature = hex(HMAC-SHA256(apiKey + timestamp + nonce + body, secretKey))
  • body adalah isi request persis seperti yang dikirim, byte per byte. Untuk GET body kosong, jadi string yang ditandatangani hanya apiKey + timestamp; query string tidak ikut ditandatangani.
  • Kirim string yang sama dengan yang Anda hash. Jangan biarkan HTTP client men-serialisasi ulang JSON-nya.
  • Secret Key tidak boleh ada di kode sisi klien (browser atau aplikasi mobile).
<?php
$baseUrl   = 'https://api.labubu4d.lol';
$apiKey    = 'API_KEY_ANDA';
$secretKey = 'SECRET_KEY_ANDA';

function gatewayRequest(string $method, string $path, ?array $payload = null): array
{
    global $baseUrl, $apiKey, $secretKey;

    $body      = $payload === null ? '' : json_encode($payload);
    $timestamp = (string) time();
    $headers   = ['X-API-Key: '.$apiKey, 'X-Timestamp: '.$timestamp, 'Content-Type: application/json'];

    if ($method === 'GET') {
        $toSign = $apiKey.$timestamp.$body;
    } else {
        $nonce    = bin2hex(random_bytes(16));
        $toSign   = $apiKey.$timestamp.$nonce.$body;
        $headers[] = 'X-Nonce: '.$nonce;
    }

    $headers[] = 'X-Signature: '.hash_hmac('sha256', $toSign, $secretKey);

    $ch = curl_init($baseUrl.$path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);

    if ($method !== 'GET') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }

    return json_decode((string) curl_exec($ch), true) ?? [];
}

$payment = gatewayRequest('POST', '/api/v1/payment/qris', [
    'amount'      => 50000,
    'customer_id' => 'testnara',
    'order_id'    => 'INV-2024-001',
]);

// $payment['data']['qr_code'] untuk dirender sendiri,
// atau arahkan pelanggan ke $payment['data']['payment_url'].

IP Whitelist

Di Callback Settings Anda dapat mendaftarkan IP server yang boleh memakai API Key live, dipisah dengan |. Jika kosong, semua IP diterima. IP lain ditolak dengan HTTP 403.

Sandbox

API Key dan Secret Key sandbox (menu 2FA & API) dipakai di base URL yang sama. Pembayaran sandbox tidak pernah dikirim ke bank atau provider, tidak menyentuh saldo asli, dan QR-nya tidak dapat dibayar. Hasilnya (berhasil atau gagal) ditentukan dari halaman Sandbox di dashboard, yang mengirim callback sungguhan bertanda tangan Secret Key sandbox. IP whitelist tidak berlaku untuk sandbox.

Format Respons

Sukses
{
  "success": true,
  "message": "OK",
  "data": { }
}
Gagal
{
  "success": false,
  "error": "Invalid API credentials",
  "code": 401
}

Kode Error

HTTPArti
200Sukses
400Parameter tidak valid (pesan menyebut field-nya)
401Header autentikasi kurang, signature salah, timestamp di luar rentang, nonce sudah dipakai, atau akun nonaktif
403IP tidak ada di whitelist
404Transaksi atau path tidak ditemukan
429Rate limit terlampaui
500Kesalahan internal
502 / 503Provider pembayaran bermasalah (keduanya dikirim sebagai HTTP 503; field code menyebut yang sebenarnya). Cek status transaksi sebelum membuat ulang.

Buat Pembayaran QRIS

POST/api/v1/payment/qris
ParameterTipeWajibKeterangan
amountintegerYaNominal dalam IDR. Min Rp 10.000, maks Rp 10.000.000.
customer_idstringYaPengenal pelanggan (username, email, telepon, atau ID unik). Maks. 191 karakter.
order_idstringTidakReferensi order atau invoice Anda (A-Z a-z 0-9 . _ : -, maks. 64). Setiap request membuat pembayaran baru, termasuk dengan order_id yang sudah pernah dipakai.
descriptionstringTidakKeterangan pembayaran.
callback_urlstringTidakDiabaikan; lihat Pindah dari integrasi lain.
Respons (data)
{
  "transaction_id": "TRX2610241234567",
  "order_id": "INV-2024-001",
  "customer_id": "testnara",
  "transaction_status": "pending",
  "transaction_time": "2026-10-24 10:00:00",
  "payment_type": "qris",
  "gross_amount": 50000,
  "amount_paid": 50000,
  "fee_borne_by": "merchant",
  "fee_from_customer": 0,
  "fee_amount": 350,
  "net_amount": 49650,
  "currency": "IDR",
  "payment_url": "https://api.labubu4d.lol/pay/TRX2610241234567",
  "payment_url_direct": "https://api.labubu4d.lol/pay/TRX2610241234567",
  "qr_code": "00020101021226...",
  "expiry_time": "2026-10-24 10:10:00",
  "sandbox": false
}
  • gross_amount: nominal yang Anda minta, tidak berubah. amount_paid: yang dibayar pelanggan lewat QR.
  • fee_amount dan net_amount pada pembayaran yang belum lunas adalah perkiraan dari tarif saat ini; angka final tercatat saat pembayaran lunas.
  • qr_code: string QRIS untuk dirender menjadi gambar QR di aplikasi Anda. payment_url: halaman pembayaran kami; arahkan pelanggan ke sana jika tidak ingin merender sendiri.
  • QRIS berlaku 10 menit (expiry_time).

Cek Status Pembayaran

GET/api/v1/payment/{transaction_id}

{transaction_id} dapat berupa ID TRX… atau order_id Anda. Jika order_id dipakai untuk lebih dari satu pembayaran, yang dikembalikan adalah yang terbaru. Respons berisi field yang sama dengan pembuatan, ditambah settlement_time setelah lunas. Untuk pembayaran yang masih pending, kami memeriksa provider lebih dulu, sehingga callback yang terlewat tetap terlihat di sini.

Status Publik

GET/api/v1/payment/status?id={transaction_id}

Tanpa autentikasi, untuk halaman pembayaran milik pelanggan. Hanya menerima ID TRX… dan hanya mengembalikan status dan waktu (tanpa nominal atau data akun).

Respons (data)
{
  "status": "pending",
  "transaction_time": "2026-10-24 10:00:00",
  "expiry_time": "2026-10-24 10:10:00",
  "settlement_time": null
}

Daftar Transaksi

GET/api/v1/transactions
QueryKeterangan
pageNomor halaman, default 1
per_page (alias limit)Item per halaman, default 20, maks. 100
statussettlement, pending, expire, deny, cancel
order_idFilter berdasarkan order Anda
date_from, date_toYYYY-MM-DD, berdasarkan waktu pembuatan

Urutan terbaru lebih dulu. data.transactions berisi objek transaksi, dan data.pagination berisi page, per_page, total, total_pages. Query string tidak ikut ditandatangani.

Nilai Status

StatusArti
pendingMenunggu pembayaran pelanggan
settlementPembayaran diterima
denyPembayaran ditolak atau gagal
expireWaktu pembayaran habis
cancelDibatalkan (tidak dipakai saat ini)

Halaman Pembayaran

GET/pay/{transaction_id}

Halaman milik kami yang menampilkan nominal, QR, hitung mundur, dan status yang diperbarui otomatis sampai lunas. Inilah payment_url. Tidak perlu autentikasi.

Saldo

GET/api/v1/balance
Respons (data)
{
  "available": 15000000,
  "pending": 250000,
  "available_formatted": "Rp 15.000.000",
  "pending_formatted": "Rp 250.000"
}

available: saldo yang dapat ditarik. pending: dana yang sedang ditahan untuk penarikan yang berjalan. Kunci sandbox selalu mendapat 0.

Health Check

GET/api/v1/health

Tanpa autentikasi. Mengembalikan {"status": "ok", "timestamp": …} di dalam data.

Callback Pembayaran

Saat sebuah pembayaran mencapai status akhir (settlement, deny, atau expire), kami mengirim POST JSON ke Callback URL Anda.

HeaderKeterangan
Content-Typeapplication/json
X-Callback-SignatureHMAC-SHA256 hex untuk verifikasi
X-Callback-TimestampUnix time saat callback dikirim
Body
{
  "transaction_id": "TRX2610241234567",
  "order_id": "INV-2024-001",
  "customer_id": "testnara",
  "transaction_status": "settlement",
  "transaction_time": "2026-10-24 10:00:00",
  "settlement_time": "2026-10-24 10:02:11",
  "payment_type": "qris",
  "gross_amount": 50000,
  "amount_paid": 50000,
  "fee_borne_by": "merchant",
  "fee_from_customer": 0,
  "fee_amount": 350,
  "net_amount": 49650,
  "currency": "IDR"
}

Body juga memuat field tambahan format native kami (status, amount, idrpayment_id, signature, signature_v2, sandbox). Abaikan saja jika tidak dipakai. Semua field di body ikut ditandatangani.

Verifikasi Signature

  1. Baca body mentah dan header X-Callback-Signature.
  2. Decode JSON, urutkan key tingkat atas (ksort).
  3. Encode ulang tanpa spasi dengan JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES.
  4. Hitung HMAC-SHA256(hasil, secretKey) dalam hex dan bandingkan dengan hash_equals.
  5. Jika tidak sama, balas 401. Jika sama, proses transaction_status lalu balas 200.

Secret Key yang dipakai adalah milik akun Anda (Secret Key sandbox untuk pembayaran sandbox). X-Callback-Timestamp tidak ikut ditandatangani. Cocokkan juga gross_amount dengan pesanan Anda sebelum menandai lunas.

<?php
$secretKey = 'SECRET_KEY_ANDA';

$raw       = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CALLBACK_SIGNATURE'] ?? '';

$payload = json_decode($raw, true);
if (! is_array($payload)) {
    http_response_code(400);
    exit;
}

ksort($payload);
$expected = hash_hmac(
    'sha256',
    json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
    $secretKey
);

if (! hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

if ($payload['transaction_status'] === 'settlement') {
    // tandai $payload['order_id'] lunas, cocokkan gross_amount dengan pesanan Anda
}

http_response_code(200);
echo 'OK';

Pengiriman & Retry

  • Endpoint Anda harus membalas HTTP 2xx dalam beberapa detik (batas waktu default 5 detik; dapat diatur per akun).
  • Jika tidak, kami mengulang sesuai pengaturan akun (default 3 percobaan, dengan jeda 10 detik, 1 menit, lalu 5 menit).
  • Callback yang gagal permanen tampak di dashboard, menu Callbacks, dan dapat dikirim ulang dari sana.
  • Callback yang sama bisa terkirim lebih dari sekali. Buat penanganannya idempoten berdasarkan transaction_id.
  • URL callback harus HTTPS dan mengarah ke alamat publik; pengalihan (redirect) tidak diikuti.