API payment gateway QRIS: cara kerja, fitur wajib, dan cara integrasinya
Cara kerja API payment gateway QRIS: buat QRIS dinamis per pesanan, terima webhook, uji di sandbox, aturan keamanan, dan biayanya. Dengan contoh kode.
Tim WaroengPay · Diperbarui · 11 menit baca

API payment gateway QRIS adalah antarmuka HTTP yang membuat servermu bisa membuat tagihan QRIS dinamis untuk setiap pesanan, lalu diberi tahu secara otomatis begitu pesanan itu dibayar. Alurnya selalu sama: servermu memanggil satu endpoint untuk membuat tagihan, pembeli memindai QR yang nominalnya sudah terisi, lalu payment gateway mengirim webhook atau kamu mengecek status lewat API. Panduan ini menjelaskan cara kerjanya, endpoint dan fitur yang wajib ada, contoh kode, cara menguji di sandbox, aturan keamanan, sampai soal biaya, supaya kamu bisa menilai API QRIS mana pun dan memasangnya dengan benar.
Cara kerja API QRIS dalam empat langkah
- Buat tagihan. Saat pembeli checkout, servermu mengirim nominal pesanan ke endpoint pembuatan tagihan. Respons berisi id tagihan, string QRIS dinamis, nominal akhir, dan alamat halaman bayar.
- Tampilkan pembayaran. Arahkan pembeli ke halaman bayar milik payment gateway, atau buat gambar QR sendiri dari string QRIS dan tampilkan di halaman checkout-mu.
- Pembeli membayar. Pembeli memindai QR dari aplikasi m-banking atau e-wallet apa pun. Nominalnya sudah terisi, jadi tidak ada salah ketik.
- Pesanan lunas otomatis. Payment gateway mendeteksi pembayaran, mengubah status tagihan menjadi lunas, lalu mengirim webhook ke servermu. Servermu memverifikasi webhook dan memproses pesanan.
Kata kuncinya adalah QRIS dinamis: kode QR yang dibuat per transaksi dan sudah memuat nominal. Tanpa itu, pembeli mengetik nominal sendiri dan pembayaran sulit dicocokkan otomatis. Bedanya dengan QRIS statis dijelaskan di perbedaan QRIS statis dan dinamis.
Bagaimana API tahu tagihan sudah dibayar
Bagian ini yang paling sering luput saat memilih API QRIS. Membuat QR itu mudah; yang sulit adalah mengetahui dengan pasti bahwa pembayaran yang masuk milik tagihan yang mana. Cara pencocokan berbeda antar penyedia, jadi tanyakan atau baca dokumentasinya sebelum memilih.
Di WaroengPay, pencocokan memakai nominal unik. Setiap tagihan mendapat kode unik kecil (default Rp100 sampai Rp250) yang ditambahkan ke harga, sehingga setiap tagihan yang sedang menunggu punya nominal berbeda. Selama ada tagihan terbuka, mutasi rekening merchant dibaca setiap 8 detik. Kredit dengan nominal persis sama dalam jendela waktu tagihan menandai tagihan itu lunas. Satu kredit hanya bisa melunasi satu tagihan, jadi tidak ada konfirmasi ganda. Penjelasan lengkap mekanismenya ada di panduan konfirmasi pembayaran otomatis dan apa itu kode unik transfer.
Konsekuensinya untuk developer: pembeli wajib membayar nominal persis, termasuk kode unik. Tampilkan field amount dari respons apa adanya, jangan dibulatkan.
Fitur yang wajib ada di API payment gateway QRIS
Gunakan tabel ini sebagai daftar periksa saat membandingkan penyedia. Kolom terakhir menunjukkan cara WaroengPay memenuhinya.
| Kemampuan | Kenapa penting | Di WaroengPay |
|---|---|---|
| Buat tagihan QRIS dinamis | Nominal terisi, setiap pesanan punya tagihan sendiri | POST /api/payments |
| Cek status tagihan | Cadangan kalau webhook terlambat atau servermu sempat mati | GET /api/payments/:id |
| Daftar dan filter tagihan | Rekonsiliasi harian dan laporan | GET /api/payments dengan filter status, tanggal, dan pencarian |
| Batalkan tagihan | Pembeli ganti pesanan, stok habis | POST /api/payments/:id/cancel |
| Webhook bertanda tangan | Servermu bisa memastikan kiriman benar dari payment gateway | HMAC-SHA256 dengan timestamp, dikirim ulang bila gagal |
| Idempotency key | Tombol checkout terklik dua kali tidak membuat dua tagihan | Header Idempotency-Key |
| Mode uji (sandbox) | Uji alur lengkap tanpa uang sungguhan | API key wp_test_ dan endpoint simulasi |
| Kode error yang jelas | Servermu bisa bereaksi tepat, bukan sekadar "gagal" | JSON { error, code } dengan status HTTP standar |
| Halaman bayar siap pakai | Tidak perlu membuat tampilan QR sendiri | pay_url dengan QR, nominal, dan hitung mundur |
Membuat tagihan QRIS dinamis lewat API
Semua request memakai HTTPS dan JSON, dengan API key di header Authorization: Bearer. Nominal dikirim dalam rupiah sebagai bilangan bulat, antara Rp1.000 dan Rp10.000.000. Contoh dalam beberapa bahasa:
curl -X POST https://waroengpay.com/api/payments \-H "Authorization: Bearer $WAROENGPAY_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: INV-2041" \-d '{"amount": 50000,"reference": "INV-2041","description": "Paket Premium 1 bulan","callback_url": "https://tokoku.id/webhook/waroengpay","redirect_url": "https://tokoku.id/pesanan/INV-2041"}'
Field opsional yang paling berguna:
reference: nomor pesanan di sistemmu. Ikut di webhook, tapi tidak unik, jadi jangan dipakai untuk mencocokkan pembayaran.callback_url: alamat webhook khusus tagihan ini. Kosong berarti memakai URL webhook default akun.redirect_url: pembeli diarahkan ke sini 6 detik setelah lunas.expires_in: masa berlaku dalam menit, 5 sampai 60. Defaultnya 15 menit.
Respons (dipersingkat):
{"id": "WRG-7K2M9QX4BD1PZ","reference": "INV-2041","status": "pending","base_amount": 50000,"unique_code": 137,"amount": 50137,"fee": 137,"net_amount": 50000,"qris": "000201010212265...6304A1B2","pay_url": "https://waroengpay.com/pay/WRG-7K2M9QX4BD1PZ","expires_at": "2026-09-24T03:15:00.000Z","livemode": true}
| Field | Artinya |
|---|---|
id | Id tagihan (WRG-...). Simpan di pesananmu, karena webhook dicocokkan lewat id ini. |
amount | Nominal yang harus dibayar pembeli, sudah termasuk kode unik. |
base_amount | Harga yang kamu kirim. Bandingkan dengan total pesanan saat webhook datang. |
fee | Biaya layanan tagihan ini, dikunci saat tagihan dibuat. net_amount = amount − fee masuk ke saldo. |
qris | String QRIS dinamis untuk dibuat gambar QR sendiri. |
pay_url | Halaman bayar siap pakai. |
livemode | false untuk tagihan mode uji. |
Menampilkan QR ke pembeli
Ada dua pilihan, dan keduanya sama-sama otomatis:
- Arahkan ke
pay_url. Cara tercepat. Halaman bayar sudah berisi QR, nominal persis, hitung mundur, nama dan logo tokomu, lalu berubah sendiri begitu lunas. Setelah lunas, pembeli bisa mengunduh bukti bayar. - Render QR sendiri. Buat gambar QR dari string
qrisdengan pustaka QR apa pun, tampilkanamountpersis dan batas waktunya, lalu pantau statusnya. Cocok kalau checkout-mu harus tetap di satu halaman, misalnya di aplikasi mobile atau bot.
Mengetahui pembayaran lunas: webhook atau polling
Ada dua cara servermu mengetahui tagihan sudah lunas. Untuk produksi, pakai webhook sebagai jalur utama dan cek status sebagai cadangan.
| Webhook | Polling status | |
|---|---|---|
| Cara kerja | WaroengPay mengirim POST ke servermu saat status jadi final | Servermu memanggil GET /api/payments/:id berulang |
| Butuh alamat publik | Ya, port 80, 443, 8080, atau 8443 | Tidak |
| Kecepatan | Segera setelah lunas terdeteksi | Tergantung jarak polling, cukup tiap 5 sampai 10 detik |
| Cocok untuk | Website dan aplikasi dengan backend | Skrip sederhana, bot, server tanpa alamat publik |
Webhook
Event yang dikirim: payment.paid, payment.expired, payment.cancelled, dan ping dari tombol "Kirim tes" di dashboard. Setiap kiriman membawa header X-WaroengPay-Timestamp dan X-Signature, yaitu HMAC-SHA256 dari "<timestamp>.<body mentah>" dengan secret webhook-mu. Contoh verifikasi lengkap:
import crypto from "node:crypto";import express from "express";const app = express();const SECRET = process.env.WAROENGPAY_WEBHOOK_SECRET; // whsec_...// pakai body mentah: tanda tangan dihitung dari teks persis yang dikirimapp.post("/webhook/waroengpay", express.raw({ type: "application/json" }), async (req, res) => {const ts = req.get("x-waroengpay-timestamp") || "";const sig = req.get("x-signature") || "";const expected = crypto.createHmac("sha256", SECRET).update(`${ts}.${req.body}`).digest("hex");const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300; // tolak kiriman ulang > 5 menitif (!fresh || sig.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {return res.sendStatus(401);}const event = JSON.parse(req.body);if (event.livemode === false) return res.sendStatus(200); // event mode uji: jangan pernah menandai pesanan asliif (event.event === "payment.paid") {// cari pesanan lewat id tagihan yang disimpan saat membuat tagihan, BUKAN reference (tidak unik, bebas diisi)const order = await db.orders.findByWaroengpayId(event.data.id);if (!order || event.data.base_amount !== order.total) return res.sendStatus(200); // nominal beda: jangan tandai lunasawait db.orders.markPaid(order.id, event.id); // lewati kalau event.id sudah pernah diproses}res.sendStatus(200);});
Balas dengan status 2xx dalam 10 detik dan kerjakan proses berat di belakang. Kalau servermu gagal membalas, kiriman dicoba ulang: langsung, lalu setelah 30 detik, 2 menit, 10 menit, 30 menit, dan 2 jam. Karena itu event yang sama bisa datang lebih dari sekali; simpan id event dan lewati duplikat. Penjelasan setiap langkah verifikasi ada di panduan webhook pembayaran.
Polling
Tanpa webhook, cek status sampai tidak lagi pending:
async function tungguLunas(id) {for (;;) {const res = await fetch(`https://waroengpay.com/api/payments/${id}`, {headers: { Authorization: `Bearer ${process.env.WAROENGPAY_API_KEY}` },});const tagihan = await res.json();if (tagihan.status !== "pending") return tagihan; // paid | expired | cancelledawait new Promise((r) => setTimeout(r, 5000)); // cukup tiap 5 sampai 10 detik}}
Menguji di sandbox tanpa uang sungguhan
Buat API key mode uji (wp_test_...) di dashboard → Integrasi & API → Kredensial, lalu panggil endpoint yang sama persis. Tagihan uji punya livemode: false, tidak bisa dibayar aplikasi apa pun, dan tidak pernah masuk ke saldo. Kamu melunasinya lewat tombol Simulasikan bayar di halaman bayar atau lewat endpoint simulasi:
curl -X POST https://waroengpay.com/api/payments/WRG-7K2M9QX4BD1PZ/simulate \-H "Authorization: Bearer $WAROENGPAY_TEST_KEY" \-H "Content-Type: application/json" \-d '{"action":"pay"}' # atau "expire"
Tiga hal yang sering membuat bingung saat menguji:
- Webhook tagihan uji hanya dikirim ke
callback_urlyang diisi saat membuat tagihan. URL webhook default akun tidak dipakai. - Webhook uji ditandatangani dengan secret mode uji (
whsec_test_...), bukan secret live. - Alamat localhost tidak bisa menerima webhook. Pakai layanan tunnel seperti ngrok atau Cloudflare Tunnel untuk mendapat alamat publik sementara.
Uji minimal tiga skenario: lunas ("action":"pay"), kedaluwarsa ("action":"expire"), dan webhook yang kamu balas dengan error supaya terlihat pengiriman ulangnya.
Aturan keamanan integrasi QRIS
- API key hanya di server. Simpan di variabel lingkungan, jangan di kode frontend, aplikasi mobile, atau repository. Curiga bocor? Buat ulang di dashboard; key lama langsung mati. Kartu API key menampilkan kapan dan dari IP mana key terakhir dipakai.
- Jangan anggap lunas karena pembeli kembali ke
redirect_url. Alamat itu bisa dibuka siapa saja. Status lunas hanya dari webhook atau dari API. - Verifikasi tanda tangan dari body mentah dan tolak kiriman yang timestamp-nya lebih dari 5 menit.
- Cocokkan lewat
data.iddan nominal. Tanda tangan valid hanya membuktikan event berasal dari WaroengPay, bukan bahwa event itu untuk pesanan yang kamu kira. Pastikandata.base_amountsama dengan total pesanan. - Abaikan
livemode: falsedi server produksi. - Siapkan pembayaran telat. Pembayaran yang masuk setelah tagihan kedaluwarsa bisa dicocokkan admin; kamu lalu menerima
payment.paiddengandata.late = truesetelah sebelumnya menerimapayment.expired. Jangan menolaknya otomatis: proses pesanan atau hubungi pembeli.
Batasan dan kode error
| Batasan | Nilai |
|---|---|
| Nominal per tagihan | Rp1.000 sampai Rp10.000.000, ditambah kode unik |
| Masa berlaku | 5 sampai 60 menit (default 15), ditambah 5 menit masa tenggang |
| Tagihan dibuat | Maksimal 30 per menit per akun |
| Tagihan menunggu | Maksimal 50 per akun, dan 25 dengan nominal yang sama |
| Request | Maksimal 300 per menit per IP, body JSON maksimal 20 KB |
| Kode | Artinya | Yang perlu dilakukan |
|---|---|---|
400 invalid_amount | Nominal bukan bilangan bulat dalam batas | Kirim angka rupiah tanpa titik ribuan, misalnya 50000 |
401 invalid_api_key | API key salah atau sudah dibuat ulang | Periksa variabel lingkungan |
403 account_not_active | Akun belum disetujui atau nonaktif | Tunggu persetujuan admin atau hubungi kami |
429 rate_limited | Terlalu banyak request | Tunggu sesuai header Retry-After |
429 too_many_pending | Terlalu banyak tagihan menunggu | Batalkan tagihan lama atau tunggu selesai |
503 amount_busy | Kode unik untuk nominal itu sedang habis | Coba lagi beberapa saat kemudian |
Berapa biaya API QRIS?
Banyak orang mencari "API QRIS gratis". Yang perlu dibedakan adalah biaya akses API dan biaya per transaksi. Di WaroengPay, API, webhook, dan mode uji tidak dikenai biaya, begitu juga pendaftaran, biaya bulanan, dan penarikan dana. Per transaksi, kode unik yang dibayar pembeli menjadi biaya layanan, jadi harga barang diterima utuh. Untuk harga di atas Rp500.000 ada tambahan 0,7% dari harga.
| Contoh | Harga Rp50.000 | Harga Rp600.000 |
|---|---|---|
| Pembeli membayar | Rp50.137 | Rp600.201 |
| Biaya WaroengPay | Rp137 (kode unik) | Rp4.401 (kode unik + 0,7%) |
| Masuk ke saldomu | Rp50.000 | Rp595.800 |
Angka kode unik di atas hanya contoh; nilai sebenarnya ada di field unique_code setiap tagihan. Rincian lengkap dan jadwal pencairan ada di halaman harga. Untuk membandingkan dengan penyedia lain, baca perbandingan payment gateway QRIS untuk UMKM.
Panduan integrasi per bahasa
- Integrasi QRIS di PHP dan Laravel: dari checkout, webhook, sampai pengecualian CSRF di Laravel.
- Webhook pembayaran: verifikasi HMAC, idempotensi, dan pengiriman ulang, dengan contoh Node.js, PHP, dan Python.
- Dokumentasi API: referensi semua endpoint, termasuk contoh Node.js dan Python. Versi Markdown-nya ada di
/docs.md, praktis untuk dibaca asisten AI saat menulis integrasi.
Tidak punya backend atau belum ingin coding? Link pembayaran dibuat dari dashboard tanpa API key dan bisa dibagikan di chat atau bio media sosial. Setiap pembeli tetap mendapat tagihan QRIS sendiri. Gambaran semua pilihan ada di panduan lengkap terima pembayaran QRIS di toko online.
Checklist sebelum go live
- Alur lengkap sudah diuji di mode uji: lunas, kedaluwarsa, dan webhook yang dikirim ulang.
- API key diganti ke key live (
wp_live_...) dan secret webhook ke secret live. - URL webhook default terisi, dan tombol Kirim tes di dashboard mendapat balasan 2xx.
- Pesanan hanya lunas lewat webhook atau status API, dicocokkan lewat id tagihan dan nominal.
payment.paiddenganlate: trueditangani.- Satu transaksi kecil dengan uang sungguhan berhasil lunas sendiri.
Pertanyaan yang sering muncul
Apakah butuh mesin EDC atau rekening merchant sendiri?
Tidak. Pembayaran diterima di rekening merchant QRIS yang terhubung dengan WaroengPay. Saldo setiap toko dicatat terpisah dan dicairkan ke rekening atas nama pemilik toko.
Bagaimana cara mendapatkan API key?
Daftar akun, tunggu persetujuan admin, lalu buat API key di dashboard → Integrasi & API → Kredensial. Key mode uji bisa dipakai untuk mencoba alur dari awal sampai webhook.
Seberapa cepat status berubah lunas?
Biasanya dalam belasan detik setelah pembeli membayar, karena mutasi dibaca setiap 8 detik selama ada tagihan terbuka. Kecepatannya tetap bergantung pada mutasi bank. Konfirmasi berjalan 24 jam.
Bisakah satu tagihan dibayar dua kali?
Tidak. Satu kredit di mutasi hanya bisa melunasi satu tagihan, dan tagihan yang sudah lunas tidak bisa dilunasi lagi. Kredit kedua dicatat terpisah untuk ditelusuri.
Apakah link pembayaran bisa dibuat lewat API?
Tidak, link pembayaran dibuat di dashboard. Tagihan dari link tetap muncul di GET /api/payments dengan link_id terisi dan mengirim webhook ke URL default akunmu.


