# Konek QR API > 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_…` or `kq_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 `Origin` header (a browser) is refused with `403 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_limited` with `Retry-After` in seconds). An account under anti-abuse review is held to 10 live invoices an hour, answered the same way. - **Errors**: RFC 9457 `application/problem+json` with a stable `code` to switch on. `detail` and field `message`s are Indonesian unless `Accept-Language` prefers English (`en`). - **Test mode**: test invoices have a QR that no payer app accepts (`ID.KONEKQR.TEST`, `KONEK QR TEST`). Mark one paid with `POST /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, scope `payments:write`): Create an invoice. Creates an invoice of `amount` plus (or minus, per QRIS) a unique code of Rp1–499 (never 100, 200, 300 or 400), so the total identifies the payment. The response holds `qrString` (the dynamic QRIS to show the buyer) and `payUrl` (a hosted payment page). - `GET /v1/payments` (listPayments, scope `payments:read`): List invoices. Invoices of the key's mode, newest first. `qrString` is always `null` in lists; get one invoice for it. Page with `before` (the `id` of the last invoice you got). - `GET /v1/payments/{id}` (getPayment, scope `payments:read`): Get an invoice. One invoice of the key's mode, with `qrString` while it is `pending`. - `POST /v1/payments/{id}/cancel` (cancelPayment, scope `payments:write`): Cancel an invoice. Cancels a pending invoice (also one past its expiry) and emits `payment.cancelled`. A buyer who pays it anyway lands in the dashboard's unmatched box. No request body. - `POST /v1/payments/{id}/simulate` (simulatePayment, scope `payments:write`): Simulate a payment (test mode). Marks a pending **test** invoice paid as the Listener would (`paidVia: "simulation"`, `paidLate` when past its expiry) and emits `payment.paid`, so your webhook handler and event consumer see what a real payment looks like. Test keys only: a live key gets `403 test_mode_only`. No request body. - `GET /v1/qris` (listQris, scope `payments:read`): List usable QRIS. The QRIS invoices of the key's mode can use, oldest first: `active` ones for a live key, every QRIS of the account for a test key. - `GET /v1/events` (listEvents, scope `events:read`): Read account events. Events of the key's mode after the cursor `after`, oldest first, at most 100. Without `after` it starts at the oldest event kept (30 days). Store `next` and send it as `after` on the next call: nothing is skipped or repeated. With `wait` (seconds) the request waits until an event arrives or the time is up (long-polling); use a client timeout of at least `wait` + 10 seconds. At most 5 requests of an account wait at once; more answer at once. - `GET /v1/devices` (listDevices, scope `payments:read`): List Listener phones. The account's paired Konek QR Listener phones, oldest first. `online` means 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`). Check `paidLate`: a payment up to 24 hours after expiry is still matched, as late. - `payment.expired`: A pending invoice passed `expiresAt` (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. Always `livemode: true`. The notification text is never included. - `device.offline`: A Listener phone sent nothing for 7 minutes (once per outage). Always `livemode: true`. - `device.online`: The first signed request of a phone after `device.offline`. Always `livemode: true`. - `topup.paid`: A top-up of your Konek QR fee balance (made in the dashboard) was paid. Always `livemode: 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 in `GET /v1/events`.