Tagihan lewat API, kabar lunas lewat webhook bertanda tangan.
REST API v1 dengan OpenAPI 3.1, webhook Standard Webhooks ke beberapa endpoint, event yang bisa dibaca ulang 30 hari, dan mode uji untuk mencoba semuanya tanpa uang sungguhan.
Satu permintaan, satu tagihan.
Kirim nominal, Konek QR menambahkan kode unik dan membalas dengan QR, link halaman bayar, dan total yang harus dibayar. Arahkan pembeli ke payUrl, atau tampilkan qrString dengan library QR apa saja.
| POST | /v1/paymentsBuat tagihan. Idempotency-Key wajib. | payments:write |
|---|---|---|
| GET | /v1/payments/{id}Status tagihan, plus qrString selama menunggu. | payments:read |
| GET | /v1/paymentsDaftar terbaru dulu; filter status dan referenceId. | payments:read |
| POST | /v1/payments/{id}/cancelBatalkan tagihan yang menunggu. | payments:write |
| POST | /v1/payments/{id}/simulateTandai lunas, hanya mode uji. | payments:write |
| GET | /v1/qrisQRIS yang bisa dipakai, dengan NMID. | payments:read |
| GET | /v1/eventsEvent setelah cursor, long-poll sampai 30 detik. | events:read |
| GET | /v1/devicesHP Listener dan status online-nya. | payments:read |
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, "referenceId": "INV-1001", "expiresInMinutes": 15}'{
"payment": {
"id": "01972d1c-4b8e-7c3a-9d2f-1a2b3c4d5e6f",
"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-…",
"qrString": "00020101021226…6304ABCD",
"qris": {
"provider": "shopeepay",
"merchantName": "TOKO CONTOH",
"nmid": "ID0000000000000"
}
}
}Webhook bertanda tangan, ke beberapa server.
- Format Standard Webhooks: webhook-id, webhook-timestamp, dan webhook-signature (HMAC-SHA256).
- Sampai 5 endpoint per akun, masing-masing dengan secret dan filter event sendiri. Rotasi secret menjaga yang lama berlaku 24 jam.
- Hanya HTTPS. Redirect tidak diikuti, alamat privat ditolak.
- Jawab 2xx dalam 15 detik. Kalau gagal, dicoba lagi selama sekitar 3 hari: 1 menit, 5 menit, 30 menit, 2 jam, dan seterusnya.
- Event: payment.paid, payment.expired, payment.cancelled, payment.unmatched, device.offline, device.online, topup.paid.
Dokter Webhook
- 403Buat WAF skip rule untuk User-Agent KonekQR-Webhooks
cloudflare_challenge - 419Kecualikan route webhook dari VerifyCsrfToken Laravel
csrf_rejected - 3xxPakai URL akhirnya; redirect tidak diikuti
redirect - TLSPerbaiki sertifikat HTTPS servermu
tls_error - 15 sJawab dulu, proses belakangan
timeout
Diagnosis muncul di log pengiriman dashboard, di samping setiap percobaan yang gagal.
POST /webhooks/konekqr HTTP/1.1
content-type: application/json
user-agent: KonekQR-Webhooks/1
webhook-id: evt_01972d1f-…
webhook-timestamp: 1793784672
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{"id":"evt_01972d1f-…","type":"payment.paid","livemode":false,
"createdAt":"2026-11-04T09:31:12Z","data":{ …the invoice… }}import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyKonekQRWebhook(secret, headers, rawBody, now = Math.floor(Date.now() / 1000)) {
const id = headers['webhook-id'] ?? '';
const timestamp = headers['webhook-timestamp'] ?? '';
if (!/^\d+$/.test(timestamp) || Math.abs(now - Number(timestamp)) > 300) throw new Error('stale');
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = createHmac('sha256', key).update(`${id}.${timestamp}.`).update(rawBody).digest();
for (const signature of (headers['webhook-signature'] ?? '').split(' ')) {
const [version, value = ''] = signature.split(',', 2);
const given = Buffer.from(value, 'base64');
if (version === 'v1' && given.length === expected.length && timingSafeEqual(given, expected)) {
return JSON.parse(rawBody.toString());
}
}
throw new Error('bad signature');
}Tanpa URL publik pun tetap dapat kabar.
GET /v1/events mengembalikan event setelah cursor, terlama dulu. Dengan wait=30, permintaan menunggu sampai ada event baru, jadi cukup diulang terus. Event disimpan 30 hari: konsumen yang sempat mati tinggal melanjutkan dari cursor terakhirnya.
curl "https://api.konekqr.web.id/v1/events?after=evt_01972d1f-…&types=payment.paid&wait=30" \
-H "Authorization: Bearer $KONEKQR_KEY"
# → { "events": [ … ], "next": "evt_01972d2a-…" }Coba semuanya tanpa uang sungguhan.
Key uji
Key kq_test_… hanya melihat dan membuat objek mode uji. Key live dan uji dibuat terpisah di dashboard, dan hanya ditampilkan sekali.
QR yang tidak bisa dibayar
QR tagihan uji memakai penerbit ID.KONEKQR.TEST dan nama KONEK QR TEST, jadi aplikasi pembayar menolaknya. Halaman bayarnya berlabel MODE UJI.
Simulasi lunas
POST /v1/payments/{id}/simulate menandai tagihan uji lunas dan mengirim payment.paid persis seperti pembayaran sungguhan.
Tidak memakai kuota
Transaksi uji tidak dihitung kuota maupun biaya, dan punya batas antrean sendiri.
SDK untuk Node.js dan Laravel.
@konekqr/node untuk Node.js 20.3+ dan konekqr/laravel untuk Laravel 12/13: buat dan baca tagihan, iterasi event, dan verifikasi webhook. Pasang dengan npm install @konekqr/node atau composer require konekqr/laravel.
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, 'paid');
}use KonekQR\Laravel\Facades\KonekQR;
$payment = KonekQR::createPayment(
['amount' => 25000, 'referenceId' => 'INV-1001'],
idempotencyKey: 'order-1001',
);Pengaman yang bisa kamu nyalakan.
Supaya botmu tidak mengirim barang untuk pembayaran yang tidak bisa dikonfirmasi, atau ke QRIS yang salah.
requireDetectionTagihan ditolak (409 detection_offline) kalau tidak ada HP Listener yang aktif dalam 7 menit terakhir.
expectedNmidTagihan ditolak (409 nmid_mismatch) kalau QRIS-nya bukan NMID yang kamu harapkan. API dan webhook selalu menyertakan NMID dan nama merchant.
Idempotency-KeyKirim ulang permintaan yang sama saat timeout, dan kamu mendapat tagihan yang sama, bukan tagihan kedua. Berlaku 24 jam.
Key hanya untuk server
Permintaan dengan Origin browser ditolak (403 browser_not_allowed). Scope per key: payments:read, payments:write, events:read. Batas 120 permintaan per menit per key.
Error yang bisa dibaca mesin
Semua error berformat RFC 9457 application/problem+json dengan code yang stabil. Kirim Accept-Language: en untuk pesan berbahasa Inggris. Semua kode error.
Dokumentasi lengkap.
Spesifikasi OpenAPI 3.1 juga tersedia di api.konekqr.web.id/openapi.yaml.
Sambungkan QRIS kamu, terima pembayaran pertama hari ini.
Daftar gratis, unggah QRIS statis, lalu coba di mode uji sebelum menerima uang sungguhan.