QRISGate REST API Reference
Integrasikan pembayaran QRIS dinamis ber-kode unik dan webhook mutasi otomatis ke platform website atau aplikasi Anda dalam hitungan menit.
/api/v1/transactions
Membuat sesi pembayaran baru, mengalokasikan 3-digit kode unik bebas bentrok, dan menghasilkan string QRIS Dinamis ber-Tag 54.
GoPay hanya mendukung QRIS statis: nominal harus diketik manual oleh pembeli.
Aplikasi GoPay Merchant hanya mengirim notifikasi ke penjual untuk pembayaran QRIS statis. Jika QR memuat nominal (QRIS dinamis), GoPay menganggapnya sebagai transaksi yang belum diaktifkan untuk akun tersebut dan notifikasi tidak pernah muncul — pesanan akan tertahan di status Menunggu. Karena itu, untuk merchant GoPay QRISGate selalu menampilkan QRIS statis dan pembeli mengetik nominal sendiri. Nominal tersebut sudah termasuk kode unik, jadi pastikan pembeli mengetiknya persis. DANA dan kanal lain tetap memakai QRIS dinamis bernominal terkunci.
curl -X POST https://qriskan.vercel.app/api/v1/transactions \
-H "Authorization: Bearer qg_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_order_id": "INV-101",
"amount": 50000,
"customer_name": "Budi Santoso",
"return_url": "https://merchant.com/thank-you"
}'Contoh Respon (201 Created)
{
"success": true,
"data": {
"transaction_id": "trx_89fe32c81e2b",
"external_order_id": "INV-101",
"merchant_name": "Toko Buku Nusantara",
"base_amount": 50000,
"unique_code": 142,
"total_amount": 50142,
"qr_string": "000201010212...5405501425802ID...6304XXXX",
"qr_image_base64": "data:image/png;base64,iVBORw0KGgo...",
"checkout_url": "https://qriskan.vercel.app/pay/trx_89fe32c81e2b",
"expires_at": "2026-10-22T04:30:00.000Z"
}
}Biaya Platform & Kode Unik
Berapa yang dibayar pembeli, berapa yang Anda terima, dan apa yang dipotong QRISGate.
base_amount), langsung ke rekening AndaBesaran kode unik dialokasikan acak di dalam rentang Rp 100–999, bukan ditentukan dari nominal pesanan. Ini disengaja: dua pesanan bernominal sama yang berjalan bersamaan harus menghasilkan total bayar berbeda, kalau tidak pembayaran yang masuk tidak dapat diatribusikan ke pesanan yang benar. Konsekuensinya, biaya sebuah transaksi bukan angka tetap — rentangnya di atas adalah yang dijamin.
Webhook Verifikasi Tanda Tangan (HMAC-SHA256)
Setiap kali notifikasi mutasi masuk dan cocok dengan transaksi PENDING, QRISGate segera menembakkan HTTP POST ke webhook_url merchant.
Spesifikasi Header Webhook
HMAC_SHA256(timestamp + "." + rawBody, webhook_secret){
"event": "payment.paid",
"transaction_id": "trx_8f2c4b1a05c67e1f7294b3d0",
"external_order_id": "INV-1042",
"base_amount": 150000,
"unique_code": 247,
"total_amount": 150247,
"customer_name": "Budi",
"paid_at": "2026-10-24T09:15:22.000Z",
"created_at": "2026-10-24T09:12:40.000Z"
}Payload bersifat flat — tidak ada wrapper data. Baca external_order_id langsung di level atas.
const crypto = require('crypto');
const express = require('express');
const app = express();
// express.raw() gives you the EXACT bytes QRISGate signed. Any JSON body
// parser (express.json(), body-parser) destroys that serialisation, and the
// signature will never match.
app.post('/api/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-qrisgate-signature'];
const timestamp = req.headers['x-qrisgate-timestamp'];
const secret = process.env.QRISGATE_WEBHOOK_SECRET;
// 1. Replay window — reject anything signed more than 5 minutes ago.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(401).send('Stale timestamp');
}
// 2. HMAC over the RAW body — never over a re-serialised object.
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + req.body)
.digest('hex');
// 3. Constant-time comparison, after a length check.
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(String(signature).replace(/^sha256=/, ''), 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('Invalid signature');
}
// 4. The payload is FLAT. There is no `data` wrapper.
const { event, external_order_id, total_amount } = JSON.parse(req.body.toString('utf8'));
if (event === 'payment.paid') {
// ... mark order external_order_id as paid for total_amount
}
res.json({ received: true });
});Tiga hal yang paling sering salah: mem-verifikasi body hasil JSON.stringify(req.body) (harus byte mentah), membandingkan dengan !== (harus constant-time), dan tidak memeriksa timestamp (harus ditolak bila lebih tua dari 5 menit).
Listener Mutasi Android (Endpoint Ingestion)
Aplikasi notification listener di handphone Android toko mengirimkan setiap push notifikasi masuk ke server QRISGate:
// Header:
// X-Device-Token: dt_YOUR_DEVICE_TOKEN
// Body:
{
"source_app": "com.gojek.gopaymerchant",
"title": "Pembayaran diterima",
"text": "Kamu menerima pembayaran QRIS sebesar Rp 125.480 dari pelanggan."
}