Langsung ke konten

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. 1

    Ambil API key

    Login ke dashboard merchant → API Keys. Buat key dengan environment test untuk sandbox, atau live untuk produksi.

  2. 2

    Buat transaksi

    Kirim POST /merchant/transactions dengan amount dalam Rupiah. Respons berisi qr_data dan payment_url.

  3. 3

    Tunggu konfirmasi

    Pelanggan bayar lewat QR. Anda menerima webhook transaction.paid, atau polling GET /transactions/:orderId secara berkala.

Contoh — buat transaksi
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.

PrefixEnvironmentKeterangan
zs_test_testSandbox — transaksi tidak memindahkan dana nyata.
zs_live_liveProduksi — 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

FieldTipeWajibKeterangan
amountintegerYaNominal dalam Rupiah. Minimal 1.000, maksimal 50.000.000. Bilangan bulat — tidak ada desimal.
paymentReferencestringTidakReferensi internal Anda (nomor invoice, dsb). Maksimal 100 karakter.
returnUrlstringTidakURL valid tempat pelanggan diarahkan setelah halaman bayar. Maksimal 500 karakter.
expiryMinutesintegerTidakMasa berlaku QR dalam menit. 1–120. Default sistem jika tidak diisi.
customerNamestringTidakNama pelanggan, maksimal 255 karakter.
customerPhonestringTidakNomor telepon, 10–20 digit.
customerEmailstringTidakEmail valid.
metadataobjectTidakData bebas (key-value) yang dikirim balik di webhook. Untuk rekonsiliasi.

Respons (201 Created)

Response
{
  "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.

Contoh
curl https://payment-gateway.zslabs.web.id/api/v1/transactions/ZSPG-20261019-ABC123
Respons
{
  "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.

Contoh
curl -X POST https://payment-gateway.zslabs.web.id/api/v1/merchant/transactions/ZSPG-20261019-ABC123/cancel \
  -H "X-API-Key: zs_test_abc123..."
Respons
{
  "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.

EventKapan dikirim
transaction.paidPembayaran terverifikasi dan transaksi berstatus paid.
transaction.expiredTransaksi kedaluwarsa sebelum dibayar.

Payload

Contoh — transaction.paid
{
  "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.

HeaderIsi
X-Webhook-SignatureHex HMAC-SHA256 dari timestamp.nonce.payload memakai secret webhook Anda.
X-Webhook-TimestampTimestamp dalam milidetik (epoch ms).
X-Webhook-NonceUUID unik per pengiriman — untuk cegah replay.
X-ZS-Environmenttest atau live.
Node.js — verifikasi signature
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 pembayaran

QR sudah dibuat, pelanggan belum bayar. Transaksi kedaluwarsa otomatis setelah expired_at.

paid Lunas

Pembayaran terverifikasi. Status terminal — tidak berubah lagi.

expired Kedaluwarsa

Waktu bayar habis sebelum konfirmasi. Status terminal.

cancelled Dibatalkan

Dibatalkan 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.

Format error
{
  "success": false,
  "status": 400,
  "code": "ERR_VALIDATION_001",
  "message": "Minimal Rp 1.000",
  "data": null,
  "trace_id": "req_xyz789"
}

Kode error umum

KodeHTTPArti
ERR_AUTH_011401API key tidak valid, expired, atau format salah
ERR_AUTH_002403Merchant tidak aktif
ERR_APIKEY_004403storeUuid di body tidak cocok dengan store pada API key
ERR_VALIDATION_001400amount di bawah minimal Rp 1.000
ERR_VALIDATION_002400amount di atas maksimal Rp 50.000.000
ERR_SOURCE_001503Belum ada payment source aktif di sistem
ERR_TXN_005400Cancel hanya untuk transaksi berstatus waiting
ERR_RES_001404Transaksi tidak ditemukan