Dokumentasi API
Integrasikan pembayaran QRIS ke sistem Anda. Buat transaksi lewat API, arahkan pelanggan ke halaman bayar, lalu terima notifikasi webhook saat pembayaran selesai.
Mulai cepat
Tiga langkah untuk menerima pembayaran pertama Anda.
- 1
Ambil API key
Login ke dashboard merchant → API Keys. Buat key dengan environment
testuntuk sandbox, atauliveuntuk produksi. - 2
Buat transaksi
Kirim
POST /merchant/transactionsdengan amount dalam Rupiah. Respons berisiqr_datadanpayment_url. - 3
Tunggu konfirmasi
Pelanggan bayar lewat QR. Anda menerima webhook
transaction.paid, atau pollingGET /transactions/:orderIdsecara berkala.
curl -X POST https://payment-gateway.zslabs.web.id/api/v1/merchant/transactions \
-H "X-API-Key: zs_test_abc123..." \
-H "Content-Type: application/json" \
-d '{
"amount": 150000,
"paymentReference": "INV-2026-001",
"returnUrl": "https://tokoanda.com/terima-kasih"
}'Autentikasi
Semua endpoint merchant memakai API key lewat header X-API-Key.
Tidak ada OAuth atau JWT — satu key cukup.
| Prefix | Environment | Keterangan |
|---|---|---|
zs_test_ | test | Sandbox — transaksi tidak memindahkan dana nyata. |
zs_live_ | live | Produksi — transaksi memakai dana nyata. |
Setiap key terikat ke satu store. Store transaksi ditentukan oleh key — Anda tidak perlu (dan tidak bisa) mengirim storeUuid berbeda di body.
Simpan key seperti password
API key memberi akses penuh ke transaksi store Anda. Jangan commit ke repository publik atau kirim ke frontend pelanggan.
Buat transaksi
POST https://payment-gateway.zslabs.web.id/api/v1/merchant/transactions
Request body
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
amount | integer | Ya | Nominal dalam Rupiah. Minimal 1.000, maksimal 50.000.000. Bilangan bulat — tidak ada desimal. |
paymentReference | string | Tidak | Referensi internal Anda (nomor invoice, dsb). Maksimal 100 karakter. |
returnUrl | string | Tidak | URL valid tempat pelanggan diarahkan setelah halaman bayar. Maksimal 500 karakter. |
expiryMinutes | integer | Tidak | Masa berlaku QR dalam menit. 1–120. Default sistem jika tidak diisi. |
customerName | string | Tidak | Nama pelanggan, maksimal 255 karakter. |
customerPhone | string | Tidak | Nomor telepon, 10–20 digit. |
customerEmail | string | Tidak | Email valid. |
metadata | object | Tidak | Data bebas (key-value) yang dikirim balik di webhook. Untuk rekonsiliasi. |
Respons (201 Created)
{
"success": true,
"status": 201,
"code": "SUCCESS",
"message": "Transaksi berhasil dibuat",
"data": {
"order_id": "ZSPG-20261019-ABC123",
"payment_reference": "INV-2026-001",
"amount": 150000,
"unique_code": 4321,
"total_amount": 154321,
"qr_data": "000201010212...",
"expired_at": "2026-10-19T12:30:00.000Z",
"return_url": "https://tokoanda.com/terima-kasih",
"environment": "test",
"payment_url": "https://payment-gateway.zslabs.web.id/payment/ZSPG-20261019-ABC123"
},
"trace_id": "req_abc123"
}unique_code
Kode unik 4 digit yang ditambahkan ke amount agar setiap transaksi punya total_amount berbeda — memudahkan rekonsiliasi.
payment_url
Halaman bayar untuk pelanggan. Buka di browser atau kirim sebagai link. Di dalamnya ada QR yang bisa di-scan dari aplikasi bank/e-wallet apa pun.
Cek status
GET https://payment-gateway.zslabs.web.id/api/v1/transactions/:orderId
Endpoint ini publik — tanpa API key. Dipakai pelanggan atau sistem Anda untuk mengecek apakah transaksi sudah dibayar.
curl https://payment-gateway.zslabs.web.id/api/v1/transactions/ZSPG-20261019-ABC123{
"success": true,
"status": 200,
"code": "SUCCESS",
"data": {
"order_id": "ZSPG-20261019-ABC123",
"payment_reference": "INV-2026-001",
"amount": 150000,
"unique_code": 4321,
"total_amount": 154321,
"status": "paid",
"paid_at": "2026-10-19T11:05:22.000Z",
"expired_at": "2026-10-19T12:30:00.000Z",
"merchant_name": "Toko Berkah",
"store_name": "Cabang Utama",
"environment": "test",
"dibuat_pada": "2026-10-19T10:30:00.000Z"
},
"trace_id": "req_def456"
}Jangan pakai endpoint ini sebagai satu-satunya sumber kebenaran
Untuk produksi, daftarkan webhook. Polling bisa delay beberapa detik dan tidak menjamin Anda menerima semua event (misalnya server Anda restart saat event datang).
Batalkan transaksi
POST https://payment-gateway.zslabs.web.id/api/v1/merchant/transactions/:orderId/cancel
Membatalkan transaksi yang masih waiting. Idempotent —
membatalkan transaksi yang sudah cancelled mengembalikan
sukses tanpa error.
curl -X POST https://payment-gateway.zslabs.web.id/api/v1/merchant/transactions/ZSPG-20261019-ABC123/cancel \
-H "X-API-Key: zs_test_abc123..."{
"success": true,
"status": 200,
"code": "SUCCESS",
"data": {
"order_id": "ZSPG-20261019-ABC123",
"status": "cancelled"
}
}Hanya untuk status waiting
Membatalkan transaksi yang sudah paid atau expired mengembalikan error ERR_TXN_005 (400).
Webhook
Webhook memberi notifikasi real-time ke server Anda saat terjadi perubahan status transaksi. Lebih andal daripada polling.
Cara kerja
Daftarkan URL webhook di dashboard merchant → Webhooks. Setiap kali transaksi berubah, sistem mengirim POST ke URL Anda dengan payload JSON.
| Event | Kapan dikirim |
|---|---|
transaction.paid | Pembayaran terverifikasi dan transaksi berstatus paid. |
transaction.expired | Transaksi kedaluwarsa sebelum dibayar. |
Payload
{
"event": "transaction.paid",
"environment": "test",
"data": {
"order_id": "ZSPG-20261019-ABC123",
"payment_reference": "INV-2026-001",
"amount": 150000,
"total_amount": 154321,
"status": "paid",
"paid_at": "2026-10-19T11:05:22.000Z"
}
}Verifikasi signature
Setiap request webhook membawa header signature untuk memastikan payload benar-benar berasal dari sistem ini dan tidak dimanipulasi.
| Header | Isi |
|---|---|
X-Webhook-Signature | Hex HMAC-SHA256 dari timestamp.nonce.payload memakai secret webhook Anda. |
X-Webhook-Timestamp | Timestamp dalam milidetik (epoch ms). |
X-Webhook-Nonce | UUID unik per pengiriman — untuk cegah replay. |
X-ZS-Environment | test atau live. |
const crypto = require('crypto')
function verifyWebhook(req, rawBody, secret) {
const timestamp = req.headers['x-webhook-timestamp']
const nonce = req.headers['x-webhook-nonce']
const signature = req.headers['x-webhook-signature']
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + "." + nonce + "." + rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
)
}Wajib: verifikasi signature sebelum proses
Selalu verifikasi signature dengan crypto.timingSafeEqual (bukan ===) untuk mencegah timing attack.
Jangan proses payload yang signature-nya tidak cocok.
Retry
Jika server Anda tidak merespons 2xx, sistem mengirim ulang dengan interval yang meningkat. Setelah batas percobaan habis, pengiriman ditandai gagal.
Status transaksi
Transaksi bergerak melalui state machine yang ketat. Anda tidak perlu menebak —
cukup pantau field status.
waiting Menunggu pembayaranQR sudah dibuat, pelanggan belum bayar. Transaksi kedaluwarsa otomatis setelah expired_at.
paid LunasPembayaran terverifikasi. Status terminal — tidak berubah lagi.
expired KedaluwarsaWaktu bayar habis sebelum konfirmasi. Status terminal.
cancelled DibatalkanDibatalkan lewat API atau dashboard sebelum dibayar. Status terminal.
Transisi status
waiting → paid · waiting → expired · waiting → cancelled.
Setelah terminal (paid, expired, cancelled), status tidak berubah lagi — aman untuk
dijadikan idempotency key di sistem Anda.
Error
Semua error memakai format yang sama. Cek field code untuk menentukan penanganan.
{
"success": false,
"status": 400,
"code": "ERR_VALIDATION_001",
"message": "Minimal Rp 1.000",
"data": null,
"trace_id": "req_xyz789"
}Kode error umum
| Kode | HTTP | Arti |
|---|---|---|
ERR_AUTH_011 | 401 | API key tidak valid, expired, atau format salah |
ERR_AUTH_002 | 403 | Merchant tidak aktif |
ERR_APIKEY_004 | 403 | storeUuid di body tidak cocok dengan store pada API key |
ERR_VALIDATION_001 | 400 | amount di bawah minimal Rp 1.000 |
ERR_VALIDATION_002 | 400 | amount di atas maksimal Rp 50.000.000 |
ERR_SOURCE_001 | 503 | Belum ada payment source aktif di sistem |
ERR_TXN_005 | 400 | Cancel hanya untuk transaksi berstatus waiting |
ERR_RES_001 | 404 | Transaksi tidak ditemukan |