Qris Digitals
Dokumentasi

API Qris Digitals

Semua yang perlu Anda panggil untuk menerima pembayaran QRIS: buat transaksi, tampilkan QR, lalu tunggu webhook.

Memulai

Base URLhttps://qris.digitals.id/api/v1
Formatapplication/json
NominalRupiah bulat, tanpa desimal
Batas panggilan60 permintaan per menit per merchant

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

ParameterTipeWajibKeterangan
amountintegerYaNominal tagihan dalam Rupiah.
reference_idstringYaID pesanan dari sistem Anda, maks 64 karakter, unik per merchant.
customer_namestringTidakNama pembeli.
customer_emailstringTidakEmail pembeli.
customer_phonestringTidakNomor telepon pembeli.
descriptionstringTidakKeterangan yang tampil di halaman bayar.
return_urlstringTidakHalaman bayar mengarahkan pembeli ke sini setelah lunas.
expires_inintegerTidakBatas 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
  }
}
total_amountYang dibayar pembeli. Sudah termasuk biaya bila biaya ditanggung pembeli.
net_amountYang masuk ke saldo Anda setelah lunas.
pay_urlHalaman bayar siap pakai. Arahkan pembeli ke sini bila tidak ingin membuat tampilan sendiri.
qr_stringIsi QRIS. Ubah menjadi gambar QR dengan pustaka QR apa pun.
qr_urlGambar QR (SVG) yang bisa langsung dipasang di tag img.

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.

StatusArti
pendingMenunggu pembayaran.
paidLunas. Dana masuk ke saldo tertahan.
expiredLewat batas waktu tanpa pembayaran.
cancelledDibatalkan lewat API.
failedGagal 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

HTTPPenyebab
401API key kosong atau salah.
403Merchant belum disetujui (kunci live) atau sedang dibekukan.
404Transaksi tidak ditemukan.
422Parameter tidak valid, reference_id sudah dipakai, atau nominal di luar batas Rp 1.000 sampai Rp 10.000.000.
429Terlalu banyak permintaan. Tunggu sesuai header Retry-After.
502 / 503Penyedia pembayaran sedang bermasalah. Coba lagi beberapa saat.