Dalam 10 menit kamu membuat tagihan uji, menandainya lunas lewat simulasi, dan menerima kabarnya. Semua contoh memakai curl; SDK Node dan Laravel ada di bagian akhir.
Referensi lengkap: openapi.yaml (juga di https://api.konekqr.web.id/openapi.yaml).
Cara kerjanya singkat
- Server-mu membuat tagihan dengan nominal, misalnya Rp25.000.
- Konek QR menambahkan kode unik Rp1–499 (total misalnya Rp25.193) dan membuat QRIS dinamis dari QRIS statis milikmu.
- Pembeli membayar dari aplikasi apa saja. Uangnya langsung masuk ke akun merchant-mu, bukan lewat Konek QR.
- Aplikasi Konek QR Listener di HP-mu membaca notifikasi "pembayaran diterima", Konek QR mencocokkan totalnya, lalu tagihan jadi
paid. - Server-mu diberi tahu lewat webhook, atau membacanya dari
GET /v1/events.
1. Siapkan akun
- Daftar di app.konekqr.web.id.
- QRIS → tambahkan QRIS statis dari aplikasi merchant-mu (unggah gambar atau tempel teksnya). Untuk mode uji, QRIS belum perlu diverifikasi.
- API → buat key mode uji. Key (
kq_test_…) hanya ditampilkan sekali; simpan di variabel lingkungan server, jangan di kode atau di browser.
export KONEKQR_KEY='kq_test_...'Key uji hanya melihat dan membuat objek mode uji. QR tagihan uji sengaja tidak bisa dibayar (penerbitnya ID.KONEKQR.TEST, merchant KONEK QR TEST), jadi aman dicoba.
2. Buat tagihan
curl https://api.konekqr.web.id/v1/payments \
-H "Authorization: Bearer $KONEKQR_KEY" \
-H "Idempotency-Key: order-1001" \
-H "Content-Type: application/json" \
-d '{"amount": 25000, "description": "Kopi susu 2x", "referenceId": "INV-1001", "expiresInMinutes": 15}'Jawaban 201:
{
"payment": {
"id": "01972d1c-4b8e-7c3a-9d2f-1a2b3c4d5e6f",
"kind": "invoice",
"livemode": false,
"status": "pending",
"amount": 25193,
"baseAmount": 25000,
"uniqueCode": 193,
"referenceId": "INV-1001",
"expiresAt": "2026-11-04T09:45:00Z",
"payUrl": "https://pay.konekqr.web.id/p/01972d1c-4b8e-7c3a-9d2f-1a2b3c4d5e6f",
"qrString": "00020101021226…6304ABCD",
"qris": { "id": "…", "label": "Toko Budi", "provider": "shopeepay", "merchantName": "TOKO CONTOH", "nmid": "ID0000000000000" },
"…": "…"
}
}Lalu pilih salah satu:
- Arahkan pembeli ke
payUrl: halaman bayar siap pakai dengan QR, hitung mundur, status otomatis, dan baris "Dibayar ke: nama · NMID · provider". IsireturnUrlsupaya ada tombol kembali ke tokomu. - Tampilkan QR sendiri dari
qrString(library QR apa saja), dan tulis totalnya (amount, Rp25.193) dengan jelas. Pembeli harus membayar tepat sebesar total itu.
Hal yang perlu diingat:
Idempotency-Keywajib. Kirim ulang permintaan yang sama dengan key yang sama (misalnya saat timeout) dan kamu mendapat tagihan yang sama, bukan tagihan kedua; jawabannya membawa headerIdempotency-Replayed: true. Pakai id pesananmu. Berlaku 24 jam.- Tanpa
qrisId, tagihan memakai QRIS tertua yang bisa dipakai. Daftar QRIS:GET /v1/qris. referenceIdunik per akun dan mode (409 reference_taken).metadata(objek JSON ≤ 4 KB) dikembalikan apa adanya di setiap tagihan dan event.
3. Cek status
curl https://api.konekqr.web.id/v1/payments/01972d1c-4b8e-7c3a-9d2f-1a2b3c4d5e6f \
-H "Authorization: Bearer $KONEKQR_KEY"status: pending, paid, expired (lewat expiresAt), atau cancelled. paidLate: true artinya dibayar setelah kedaluwarsa: pembayaran sampai 24 jam setelah expiresAt tetap dicocokkan, dan kamu yang memutuskan pesanan itu tetap diproses atau tidak.
Daftar tagihan: GET /v1/payments?status=paid&limit=50 (terbaru dulu; halaman berikutnya dengan before=<id terakhir>). Cari per pesanan: GET /v1/payments?referenceId=INV-1001. Batalkan: POST /v1/payments/{id}/cancel.
4. Simulasikan pembayaran (mode uji)
curl -X POST https://api.konekqr.web.id/v1/payments/01972d1c-4b8e-7c3a-9d2f-1a2b3c4d5e6f/simulate \
-H "Authorization: Bearer $KONEKQR_KEY"Tagihan menjadi paid dengan paidVia: "simulation", dan event payment.paid dikirim persis seperti pembayaran sungguhan. Di dashboard, tagihan uji punya tombol "Simulasikan lunas" yang sama.
5. Terima kabar lunas
Ada dua cara; banyak integrasi memakai keduanya.
Webhook (disarankan)
- Dashboard → Webhook → tambah endpoint: URL https publik, mode uji (endpoint menerima event live atau uji), event
payment.paid(atau semua). - Simpan secret
whsec_…yang ditampilkan. - Di server-mu, verifikasi tanda tangan lalu proses event. Contoh lengkap untuk PHP, Node, Python, dan Go: Verifikasi webhook.
- Tekan "Kirim event uji", lalu simulasikan tagihan dari langkah 4.
Isi webhook adalah Event:
{ "id": "evt_01972d1f-…", "type": "payment.paid", "livemode": false, "createdAt": "2026-11-04T09:31:12Z", "data": { "…tagihan…": "…" } }Jawab 2xx dalam 15 detik. Kalau gagal, Konek QR mencoba lagi selama sekitar 3 hari, dan log pengiriman menampilkan diagnosis Dokter Webhook.
Event API (tanpa URL publik, atau sebagai cadangan)
curl "https://api.konekqr.web.id/v1/events?types=payment.paid&wait=30" \
-H "Authorization: Bearer $KONEKQR_KEY"Jawaban: { "events": [...], "next": "evt_…" }. Simpan next dan kirim sebagai after= pada panggilan berikutnya; next tetap maju walaupun event yang lewat tidak cocok dengan types. Dengan wait=30 permintaan menunggu sampai ada event baru (long-poll), jadi cukup diulang terus. Event disimpan 30 hari: konsumen yang sempat mati tinggal melanjutkan dari cursor terakhirnya.
Event yang ada: payment.paid, payment.expired, payment.cancelled, payment.unmatched (pembayaran yang tidak cocok dengan tagihan mana pun, misalnya pembeli membayar QR statis langsung), device.offline, device.online.
6. Pindah ke live
- Verifikasi QRIS: pasang Listener (Memasang Konek QR Listener), lalu di Dashboard → QRIS tekan verifikasi dan bayar nominal kecil yang diminta ke QRIS-mu sendiri. Setelah Listener mendeteksinya, QRIS jadi
active. QRIS kedua dan seterusnya menunggu 24 jam setelah diverifikasi. - Buat key live (
kq_live_…) dan endpoint webhook live. - Pertimbangkan dua opsi pengaman di
POST /v1/payments:"requireDetection": true: tagihan ditolak (409 detection_offline) kalau tidak ada HP Listener yang aktif dalam 7 menit terakhir, supaya kamu tidak menjual barang yang pembayarannya tidak bisa dikonfirmasi."expectedNmid": "ID10…": tagihan ditolak (409 nmid_mismatch) kalau QRIS-nya bukan NMID ini.
- Pantau HP Listener:
GET /v1/devicesatau eventdevice.offline.
Error
Semua error berformat application/problem+json. Bandingkan code, bukan teksnya:
{ "type": "https://konekqr.web.id/errors/invalid_api_key", "title": "Invalid API key", "status": 401, "code": "invalid_api_key", "detail": "API key tidak ada, salah, atau sudah dicabut. …" }| Status | code | Artinya |
|---|---|---|
| 400 | idempotency_key_invalid | header Idempotency-Key kosong atau lebih dari 100 karakter |
| 400 | invalid_json | body bukan JSON yang valid, atau ada field yang tidak dikenal |
| 401 | invalid_api_key | key salah, dicabut, atau tidak dikirim |
| 403 | insufficient_scope | key tidak punya izin (payments:read, payments:write, events:read) |
| 403 | browser_not_allowed | permintaan dari browser; API key hanya untuk server |
| 403 | test_mode_only | simulasi memakai key live |
| 404 | not_found | tagihan atau QRIS tidak ada (atau milik mode lain) |
| 409 | qris_not_ready, too_many_pending, unique_codes_exhausted, reference_taken, nmid_mismatch, detection_offline, payment_not_pending, idempotency_conflict, idempotency_in_progress | lihat deskripsi di OpenAPI |
| 422 | validation_failed | field salah; rinciannya di errors[] (field, code, message) |
| 429 | rate_limited | lebih dari 120 permintaan per menit per key; tunggu sesuai Retry-After |
detail dan message berbahasa Indonesia; kirim Accept-Language: en untuk bahasa Inggris.
SDK
Node.js 20.3+ (npm install @konekqr/node):
import { KonekQR } from '@konekqr/node';
const kq = new KonekQR({ apiKey: process.env.KONEKQR_KEY });
const payment = await kq.createPayment({ amount: 25000, referenceId: 'INV-1001' }, { idempotencyKey: 'order-1001' });
for await (const event of kq.events({ types: ['payment.paid'] })) {
console.log(event.data.referenceId, 'lunas');
}Laravel 12/13 (composer require konekqr/laravel):
use KonekQR\Laravel\Facades\KonekQR;
$payment = KonekQR::createPayment(['amount' => 25000, 'referenceId' => 'INV-1001'], idempotencyKey: 'order-1001');Detail pemasangan ada di README masing-masing SDK.
Ada yang kurang jelas atau salah? Kirim ke [email protected].