API Qris Digitals
Semua yang perlu Anda panggil untuk menerima pembayaran QRIS: buat transaksi, tampilkan QR, lalu tunggu webhook.
Memulai
Alur dasarnya: panggil POST /transactions, tampilkan qr_string atau arahkan pembeli ke pay_url, lalu tandai pesanan lunas saat webhook berstatus paid tiba.
// Jawaban berhasil { "status": "success", "data": { ... } } // Jawaban gagal { "status": "error", "message": "..." }
Autentikasi
Kirim API key merchant di header Authorization. Kunci ada di dashboard, pada halaman merchant. Awalan kunci menentukan mode: sk_live_ untuk pembayaran sungguhan, sk_test_ untuk uji coba.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Jangan menaruh API key di aplikasi yang berjalan di perangkat pembeli (JavaScript di browser, aplikasi mobile). Panggil API dari server Anda.
Buat transaksi
POST /transactions
| Parameter | Tipe | Wajib | Keterangan |
|---|---|---|---|
| amount | integer | Ya | Nominal tagihan dalam Rupiah. |
| reference_id | string | Ya | ID pesanan dari sistem Anda, maks 64 karakter, unik per merchant. |
| customer_name | string | Tidak | Nama pembeli. |
| customer_email | string | Tidak | Email pembeli. |
| customer_phone | string | Tidak | Nomor telepon pembeli. |
| description | string | Tidak | Keterangan yang tampil di halaman bayar. |
| return_url | string | Tidak | Halaman bayar mengarahkan pembeli ke sini setelah lunas. |
| expires_in | integer | Tidak | Batas waktu bayar dalam menit, 5 sampai 1440. |
curl -X POST https://qris.digitals.id/api/v1/transactions \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "amount": 100000, "reference_id": "INV-1042", "customer_name": "Budi Santoso", "description": "Pesanan #1042", "return_url": "https://toko-anda.com/terima-kasih" }'
Jawaban 201
{
"status": "success",
"data": {
"trx_id": "KYR261004A8F3K2M9QX",
"reference_id": "INV-1042",
"status": "pending",
"amount": 100000,
"fee": 800,
"fee_bearer": "customer",
"total_amount": 100800,
"net_amount": 100000,
"pay_url": "https://qris.digitals.id/pay/KYR261004A8F3K2M9QX",
"qr_url": "https://qris.digitals.id/pay/KYR261004A8F3K2M9QX/qr.svg",
"qr_string": "00020101021226...",
"expires_at": "2026-10-04T15:30:00+07:00",
"paid_at": null,
"is_sandbox": false
}
}
Cek status
GET /transactions/{id}
{id} boleh berisi trx_id atau reference_id. Bentuk jawabannya sama seperti saat membuat transaksi. Pakai sebagai cadangan bila webhook terlambat; jangan dipanggil terus-menerus.
| Status | Arti |
|---|---|
| pending | Menunggu pembayaran. |
| paid | Lunas. Dana masuk ke saldo tertahan. |
| expired | Lewat batas waktu tanpa pembayaran. |
| cancelled | Dibatalkan lewat API. |
| failed | Gagal diproses penyedia pembayaran. |
Daftar transaksi
GET /transactions?status=paid&page=1&per_page=20
Mengembalikan transaksi terbaru lebih dulu. status dan per_page (maks 100) opsional. Informasi halaman ada di meta.
Batalkan transaksi
POST /transactions/{id}/cancel
Hanya untuk transaksi berstatus pending. Setelah dibatalkan, QR tidak bisa dibayar lagi dan webhook berstatus cancelled dikirim.
Hitung biaya
GET /fee?amount=100000
Biaya saat ini 0,7% + Rp 100 per transaksi. Bagian persen dibulatkan ke atas ke Rupiah terdekat. Siapa yang menanggung biaya diatur per merchant di dashboard.
{
"status": "success",
"data": {
"amount": 100000,
"fee_flat": 100,
"fee_percent": 0.7,
"fee": 800,
"fee_bearer": "customer",
"total_amount": 100800,
"net_amount": 100000
}
}
Webhook
Isi URL webhook di halaman merchant. Setiap kali status transaksi berubah menjadi paid, expired, cancelled, atau failed, kami mengirim HTTP POST berisi JSON ke URL tersebut.
{
"event": "payment.status_updated",
"trx_id": "KYR261004A8F3K2M9QX",
"reference_id": "INV-1042",
"status": "paid",
"amount": 100000,
"fee": 800,
"fee_bearer": "customer",
"total_amount": 100800,
"net_amount": 100000,
"customer_name": "Budi Santoso",
"customer_email": null,
"paid_at": "2026-10-04T14:35:12+07:00",
"created_at": "2026-10-04T14:30:00+07:00",
"is_sandbox": false
}
Verifikasi tanda tangan
Setiap webhook membawa header X-Kyuris-Signature dan X-Kyuris-Timestamp. Hitung ulang tanda tangan dengan webhook secret merchant, lalu tolak permintaan yang tidak cocok.
signature = HMAC-SHA256(timestamp + "." + raw_body, webhook_secret)
$payload = file_get_contents('php://input'); $timestamp = $_SERVER['HTTP_X_KYURIS_TIMESTAMP'] ?? ''; $signature = $_SERVER['HTTP_X_KYURIS_SIGNATURE'] ?? ''; $expected = hash_hmac('sha256', $timestamp . '.' . $payload, $webhookSecret); if (! hash_equals($expected, $signature)) { http_response_code(401); exit; } $data = json_decode($payload, true); if ($data['status'] === 'paid') { // Tandai pesanan $data['reference_id'] lunas (cek dulu supaya tidak diproses dua kali). } http_response_code(200);
const crypto = require('crypto'); app.post('/webhook/kyuris', express.raw({ type: 'application/json' }), (req, res) => { const payload = req.body.toString(); const expected = crypto .createHmac('sha256', process.env.KYURIS_WEBHOOK_SECRET) .update(`${req.headers['x-kyuris-timestamp']}.${payload}`) .digest('hex'); const given = String(req.headers['x-kyuris-signature'] || ''); if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) { return res.sendStatus(401); } const data = JSON.parse(payload); if (data.status === 'paid') { // Tandai pesanan data.reference_id lunas. } res.sendStatus(200); });
Aturan pengiriman
Balas dengan HTTP 2xx dalam 15 detik. Bila gagal, kami mengulang setelah 15 detik, 1 menit, 5 menit, lalu 30 menit (total 5 percobaan). Webhook yang sama bisa tiba lebih dari sekali, jadi pastikan pesanan tidak diproses dua kali. Riwayat pengiriman dan tombol kirim ulang ada di menu Log Webhook.
Mode uji
Kunci sk_test_ langsung aktif begitu merchant didaftarkan, tanpa menunggu persetujuan. Transaksi uji tidak memengaruhi saldo, tetapi webhook tetap dikirim dengan is_sandbox: true.
Untuk melunasi transaksi uji, buka pay_url lalu tekan tombol simulasi, atau panggil:
POST /transactions/{id}/simulate
Kode galat
| HTTP | Penyebab |
|---|---|
| 401 | API key kosong atau salah. |
| 403 | Merchant belum disetujui (kunci live) atau sedang dibekukan. |
| 404 | Transaksi tidak ditemukan. |
| 422 | Parameter tidak valid, reference_id sudah dipakai, atau nominal di luar batas Rp 1.000 sampai Rp 10.000.000. |
| 429 | Terlalu banyak permintaan. Tunggu sesuai header Retry-After. |
| 502 / 503 | Penyedia pembayaran sedang bermasalah. Coba lagi beberapa saat. |