Dynamic QRIS invoices on your own static QRIS, confirmed by the Konek QR Listener.
The public REST API of Konek QR (/v1) for your servers, plus the webhooks Konek QR sends.
- Base URL:
https://api.konekqr.web.id. JSON in, JSON out. Money is whole rupiah (integers). Times are RFC 3339 in UTC (2026-11-04T09:30:00Z). - Authentication:
Authorization: Bearer kq_live_…orkq_test_…. Create keys in the dashboard (app.konekqr.web.id → API). A key is shown once. A test key only ever sees and creates test-mode objects, a live key only live ones. Scopes:payments:read,payments:write,events:read. - Server-to-server only: a request with an
Originheader (a browser) is refused with403 browser_not_allowed, so keys never live in web pages. There is no CORS. - Rate limit: 120 requests per minute per key, and 120 invoice creations per minute per account
(
429 rate_limitedwithRetry-Afterin seconds). An account under anti-abuse review is held to 10 live invoices an hour, answered the same way. - Errors: RFC 9457
application/problem+jsonwith a stablecodeto switch on.detailand fieldmessages are Indonesian unlessAccept-Languageprefers English (en). - Test mode: test invoices have a QR that no payer app accepts (
ID.KONEKQR.TEST,KONEK QR TEST). Mark one paid withPOST /v1/payments/{id}/simulate. - Events: every change you can be told about is an Event, delivered by webhooks and readable
(with replay up to 30 days) through
GET /v1/events.
Full OpenAPI 3.1.0 description: https://api.konekqr.web.id/openapi.yaml
Endpoints
POST /v1/payments(createPayment, scopepayments:write): Create an invoice. Creates an invoice ofamountplus (or minus, per QRIS) a unique code of Rp1–499 (never 100, 200, 300 or 400), so the total identifies the payment. The response holdsqrString(the dynamic QRIS to show the buyer) andpayUrl(a hosted payment page).GET /v1/payments(listPayments, scopepayments:read): List invoices. Invoices of the key's mode, newest first.qrStringis alwaysnullin lists; get one invoice for it. Page withbefore(theidof the last invoice you got).GET /v1/payments/{id}(getPayment, scopepayments:read): Get an invoice. One invoice of the key's mode, withqrStringwhile it ispending.POST /v1/payments/{id}/cancel(cancelPayment, scopepayments:write): Cancel an invoice. Cancels a pending invoice (also one past its expiry) and emitspayment.cancelled. A buyer who pays it anyway lands in the dashboard's unmatched box. No request body.POST /v1/payments/{id}/simulate(simulatePayment, scopepayments:write): Simulate a payment (test mode). Marks a pending test invoice paid as the Listener would (paidVia: "simulation",paidLatewhen past its expiry) and emitspayment.paid, so your webhook handler and event consumer see what a real payment looks like. Test keys only: a live key gets403 test_mode_only. No request body.GET /v1/qris(listQris, scopepayments:read): List usable QRIS. The QRIS invoices of the key's mode can use, oldest first:activeones for a live key, every QRIS of the account for a test key.GET /v1/events(listEvents, scopeevents:read): Read account events. Events of the key's mode after the cursorafter, oldest first, at most 100. Withoutafterit starts at the oldest event kept (30 days). Storenextand send it asafteron the next call: nothing is skipped or repeated. Withwait(seconds) the request waits until an event arrives or the time is up (long-polling); use a client timeout of at leastwait+ 10 seconds. At most 5 requests of an account wait at once; more answer at once.GET /v1/devices(listDevices, scopepayments:read): List Listener phones. The account's paired Konek QR Listener phones, oldest first.onlinemeans the phone was heard from in the last 7 minutes.
Webhook events
Signed with Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature); the body is the Event object.
payment.paid: An invoice became paid: detected by the Listener (paidVia: listener), marked paid by hand in the dashboard or on the phone (manual), or simulated in test mode (simulation). CheckpaidLate: a payment up to 24 hours after expiry is still matched, as late.payment.expired: A pending invoice passedexpiresAt(reported once, at most about a minute late).payment.cancelled: An invoice was cancelled (API or dashboard).payment.unmatched: The Listener read a payment that matched no invoice, such as a buyer paying the static QR directly or paying a cancelled invoice. Alwayslivemode: true. The notification text is never included.device.offline: A Listener phone sent nothing for 7 minutes (once per outage). Alwayslivemode: true.device.online: The first signed request of a phone afterdevice.offline. Alwayslivemode: true.topup.paid: A top-up of your Konek QR fee balance (made in the dashboard) was paid. Alwayslivemode: true. Fees are taken from this balance; it cannot be withdrawn or transferred.webhook.test: Sent by "Kirim event uji" in the dashboard, signed like any other event. Webhooks only: it is not an account event and never appears inGET /v1/events.
The interactive reference needs JavaScript. Without it, read openapi.yaml directly.