PayQRISAPI Docs

PayQRIS API

API Documentation

Dokumentasi resmi untuk developer yang ingin mengintegrasikan pembayaran QRIS PayQRIS ke website, aplikasi, bot, atau backend. Pelajari cara autentikasi, membuat invoice, mengecek status, webhooks, dan banyak lagi.

Base URLLoading...
FormatJSON

Overview

Alur integrasi yang disarankan adalah: backend merchant membuat invoice → PayQRIS mengembalikan nominal pembayaran dan QRIS → merchant menampilkan checkout atau QR kepada pelanggan →pelanggan membayar → PayQRIS memperbarui status invoice → merchant menerima webhook. Untuk keputusan bisnis seperti mengirim produk atau memberi saldo, selalu gunakan status yang diverifikasi server, bukan hasil redirect browser.

Catatan: API Key adalah credential rahasia. Gunakan hanya dari server/backend merchant. Jangan menaruh API Key di JavaScript browser, aplikasi frontend publik, URL, screenshot, atau repository publik.

Authentication

Semua endpoint merchant pada API v1 menggunakan header X-API-Key. API Key diterbitkan dari dashboard merchant. Endpoint public payment status tidak membutuhkan API Key.

X-API-Key: YOUR_API_KEY
Content-Type: application/json

Jika credential tidak valid, tidak diberikan, atau merchant tidak diizinkan menggunakan API, server mengembalikan HTTP 401 atau 403 sesuai kondisi.

Quick Start

  1. Buat atau salin API Key dari dashboard merchant.
  2. Simpan API Key sebagai environment variable di backend.
  3. Buat invoice dengan endpoint POST /invoices.
  4. Arahkan pelanggan ke checkout_url atau tampilkan qris_image_url.
  5. Gunakan webhook atau payment-status API untuk memastikan pembayaran.
  6. Gunakan Idempotency-Key yang sama jika request create perlu diulang akibat kendala jaringan.

Create Invoice

POST/api/v1/invoices

Membuat invoice QRIS baru untuk merchant yang sedang terautentikasi. Invoice berlaku selama 15 menit. Sistem menghasilkan unique_code dan pay_amount sebagai nominal unik yang harus dibayar pelanggan. Jika mengirimkan callback_url, URL tersebut wajib menggunakan HTTPS di production.

Request Body

FieldTypeRequiredDescription
amountintegerYesNominal order dalam Rupiah. Production: Rp100–Rp1.000.000.
reference_idstringNoID order unik dari sisi merchant untuk menghubungkan invoice dengan sistem internal.
callback_urlstringNoURL webhook spesifik untuk invoice ini. Wajib menggunakan HTTPS di production.

Example Request

curl -X POST "/invoices" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: ORDER-10001" \
  -d '{"amount":20000,"reference_id":"ORDER-10001","callback_url":"https://merchant.example/webhook"}'

Example Response

{
  "success": true,
  "data": {
    "invoice_id": "2e96c65d36b56c6d",
    "reference_id": "QRIS-20260902-1E39FCD51A",
    "merchant_reference_id": "ORDER-10001",
    "merchant_name": "Your Store",
    "amount": 20000,
    "unique_code": 157,
    "pay_amount": 20157,
    "qris_url": "https://payqris.id/qr/REFERENCE_ID",
    "qris_image_url": "https://payqris.id/qr/REFERENCE_ID",
    "qris_string": "000201...",
    "checkout_url": "https://payqris.id/pay/REFERENCE_ID",
    "payment_api_url": "https://payqris.id/api/public/payment-links/REFERENCE_ID",
    "status": "PENDING",
    "expired_at": "2026-09-02 02:19:47"
  }
}

List Invoices

GET/api/v1/invoices

Mengambil daftar invoice milik merchant yang terautentikasi. Endpoint ini mendukung pagination, filter status, dan pencarian melalui query search berdasarkan invoice ID, reference ID, atau merchant reference ID.

Supported Status FilterPENDING, PAID, EXPIRED, FAILED, CANCELLED
Paginationpage (default 1), limit (1-100, default 10)

Example Request

curl "/invoices?status=PAID&page=1&limit=20" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response

{
  "success": true,
  "data": {
    "invoices": [
      {
        "invoice_id": "2e96c65d36b56c6d",
        "reference_id": "QRIS-20260902-1E39FCD51A",
        "merchant_reference_id": "ORDER-10001",
        "merchant_name": "Your Store",
        "amount": 20000,
        "unique_code": 157,
        "pay_amount": 20157,
        "status": "PAID",
        "created_at": "2026-09-02 02:04:47",
        "paid_at": "2026-09-02 02:08:12",
        "expired_at": "2026-09-02 02:19:47"
      }
    ],
    "pagination": {
      "total": 1,
      "page": 1,
      "limit": 20,
      "pages": 1
    }
  }
}

Get Invoice

GET/api/v1/invoices/:invoice_id

Mengambil detail satu invoice berdasarkan invoice_id. Invoice harus dimiliki oleh merchant yang terautentikasi. Jika invoice tidak ditemukan atau bukan milik merchant tersebut, server mengembalikan HTTP 404.

Example Request

curl "/invoices/INVOICE_ID" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response

{
  "success": true,
  "data": {
    "invoice_id": "2e96c65d36b56c6d",
    "reference_id": "QRIS-20260902-1E39FCD51A",
    "merchant_reference_id": "ORDER-10001",
    "merchant_name": "Your Store",
    "amount": 20000,
    "unique_code": 157,
    "pay_amount": 20157,
    "qris_url": "https://payqris.id/qr/REFERENCE_ID",
    "qris_string": "000201...",
    "status": "PAID",
    "failure_reason": null,
    "paid_at": "2026-09-02 02:08:12",
    "cancelled_at": null,
    "expired_at": "2026-09-02 02:19:47",
    "created_at": "2026-09-02 02:04:47",
    "updated_at": "2026-09-02 02:08:12"
  }
}

Cancel Invoice

POST/api/v1/invoices/:invoice_id/cancel

Membatalkan invoice yang masih PENDING dan belum melewati waktu kedaluwarsa. Invoice dengan status lain (seperti PAID atau EXPIRED) tidak dapat dibatalkan.

Example Request

curl -X POST "/invoices/INVOICE_ID/cancel" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response

{
  "success": true,
  "message": "Invoice cancelled successfully",
  "data": {
    "invoice_id": "INVOICE_ID",
    "reference_id": "QRIS-20260902-1E39FCD51A",
    "merchant_reference_id": "ORDER-10001",
    "merchant_name": "Your Store",
    "amount": 20000,
    "unique_code": 157,
    "pay_amount": 20157,
    "qris_url": "https://payqris.id/qr/REFERENCE_ID",
    "qris_string": "000201...",
    "status": "CANCELLED",
    "failure_reason": null,
    "paid_at": null,
    "cancelled_at": "2026-09-02 02:10:00",
    "expired_at": "2026-09-02 02:19:47",
    "created_at": "2026-09-02 02:04:47",
    "updated_at": "2026-09-02 02:10:00"
  }
}

Payment Status

GET/api/public/payment-links/:reference_idPublic (No API Key)

Endpoint public untuk mengecek status pembayaran menggunakan reference_id. Endpoint ini tidak membutuhkan API Key sehingga dapat digunakan dengan aman oleh halaman checkout frontend atau aplikasi client-side yang tidak menyimpan credential merchant.

Gunakan endpoint ini untuk membaca status dari server. Jangan pernah menganggap pembayaran berhasil hanya karena pelanggan berhasil redirect kembali ke halaman sukses Anda. Selalu pastikan status menunjukkan PAID.

Example Request

curl "/api/public/payment-links/REFERENCE_ID"

Example Response

{
  "success": true,
  "data": {
    "reference_id": "QRIS-20260902-1E39FCD51A",
    "merchant_reference_id": "ORDER-10001",
    "merchant_name": "Your Store",
    "amount": 20000,
    "unique_code": 157,
    "pay_amount": 20157,
    "qris_url": "https://payqris.id/qr/REFERENCE_ID",
    "qris_image_url": "https://payqris.id/qr/REFERENCE_ID",
    "qris_string": "000201...",
    "checkout_url": "https://payqris.id/pay/REFERENCE_ID",
    "payment_api_url": "https://payqris.id/api/public/payment-links/REFERENCE_ID",
    "status": "PAID",
    "created_at": "2026-09-02 02:04:47",
    "paid_at": "2026-09-02 02:08:12",
    "expired_at": "2026-09-02 02:19:47"
  }
}

QRIS & Checkout

Setelah memanggil endpoint Create Invoice, Anda akan menerima beberapa properti yang berguna untuk menampilkan pembayaran ke pengguna:

checkout_url

Pilihan termudah. Halaman siap pakai milik PayQRIS yang berisi instruksi pembayaran, QRIS, dan timer.

qris_image_url

URL file gambar PNG dari QRIS. Cocok jika Anda ingin menampilkan QRIS langsung di website Anda sendiri.

qris_string

Payload asli QRIS. Berguna jika Anda menggunakan library renderer QR code sendiri di aplikasi mobile atau frontend.

Penting: QRIS string atau qris_image_url boleh dibagikan ke client/frontend. Namun, API Key dan Webhook Secret harus selalu dijaga kerahasiaannya di sisi server backend.

Webhooks

Webhook dikirim menggunakan HTTP POST ke callback URL merchant. Event pembayaran yang tersedia adalah payment.pending, payment.paid, payment.expired, payment.failed, dan payment.cancelled. Dashboard juga menyediakan event webhook.test untuk menguji konfigurasi.

Example Payload

{
  "event": "payment.paid",
  "merchant_name": "Your Store",
  "invoice_id": "2e96c65d36b56c6d",
  "reference_id": "QRIS-20260902-1E39FCD51A",
  "merchant_reference_id": "ORDER-10001",
  "amount": 20000,
  "unique_code": 157,
  "pay_amount": 20157,
  "status": "paid",
  "created_at": "2026-09-02 02:04:47",
  "paid_at": "2026-09-02 02:08:12",
  "expired_at": "2026-09-02 02:19:47"
}

Webhook Signature Verification

Keamanan webhook dijamin melalui signature yang dikirimkan dalam header X-Signature. Signature ini dihitung menggunakan HMAC-SHA256 dari payload mentah menggunakan Webhook Secret Anda. Anda harus memverifikasi signature ini untuk memastikan bahwa permintaan benar-benar berasal dari PayQRIS dan datanya tidak dimanipulasi.

const crypto = require("crypto");

// Gunakan RAW request body, bukan JSON yang sudah diparse lalu di-stringify ulang.
const signature = crypto
  .createHmac("sha256", process.env.PAYQRIS_WEBHOOK_SECRET)
  .update(rawBody)
  .digest("hex");

const received = req.get("X-Signature") || "";
const valid =
  received.length === signature.length &&
  crypto.timingSafeEqual(
    Buffer.from(signature, "utf8"),
    Buffer.from(received, "utf8"),
  );

Merchant sebaiknya memvalidasi signature, reference ID, nominal, dan status, lalu membuat pemrosesan webhook yang idempotent. Balas HTTP 200 (atau 2xx lainnya) setelah event berhasil diterima/diproses.

Logs

PayQRIS menyediakan API untuk membaca log aktivitas yang terkait dengan merchant Anda. Anda dapat menggunakannya untuk debugging dan rekonsiliasi.

GET/api/v1/api-logs

Melihat request API yang Anda buat menggunakan API Key. Limit maksimum 100 record.

GET /api-logs?limit=50
X-API-Key: YOUR_API_KEY
{
  "success": true,
  "data": {
    "logs": [
      {
        "request_id": "req_abcdef",
        "method": "POST",
        "path": "/api/v1/invoices",
        "status_code": 201,
        "duration_ms": 124,
        "ip": "192.168.1.1",
        "created_at": "2026-09-02T02:04:47.123Z"
      }
    ]
  }
}
GET/api/v1/webhook-logs

Melihat riwayat pengiriman webhook ke callback URL Anda. Mendukung filter invoice_id dan success=true|false.

GET /webhook-logs?page=1&limit=20
X-API-Key: YOUR_API_KEY
{
  "success": true,
  "data": {
    "logs": [
      {
        "invoice_id": "2e96c65d36b56c6d",
        "callback_url": "https://merchant.example/webhook",
        "request_headers": {
          "Content-Type": "application/json",
          "X-Event": "payment.paid",
          "X-Signature": "..."
        },
        "request_body": {
          "event": "payment.paid",
          "merchant_name": "Your Store",
          "invoice_id": "2e96c65d36b56c6d",
          "reference_id": "QRIS-20260902-1E39FCD51A",
          "merchant_reference_id": "ORDER-10001",
          "amount": 20000,
          "unique_code": 157,
          "pay_amount": 20157,
          "status": "paid",
          "created_at": "2026-09-02 02:04:47",
          "paid_at": "2026-09-02 02:08:12",
          "expired_at": "2026-09-02 02:19:47"
        },
        "response_status": 200,
        "response_body": "{"ok":true}",
        "success": true,
        "duration_ms": 245,
        "attempts": 1,
        "created_at": "2026-09-02T02:08:13.456Z"
      }
    ],
    "pagination": {
      "total": 1,
      "page": 1,
      "limit": 20,
      "pages": 1
    }
  }
}

Errors

Error API dikembalikan dengan status kode HTTP yang sesuai, serta payload JSON yang konsisten yang menjelaskan alasan error tersebut.

{
  "success": false,
  "error": "Nominal transaksi tidak sesuai dengan rentang yang diizinkan."
}
400 Bad Request

Input tidak valid, parameter wajib hilang, nominal tidak valid, atau operasi yang diminta tidak diizinkan pada status saat ini.

401 Unauthorized

API Key tidak valid, sudah dicabut, atau tidak disertakan dalam request header.

403 Forbidden

Merchant terautentikasi tetapi tidak memiliki akses ke fitur tertentu atau akun sedang ditangguhkan.

404 Not Found

Invoice, resource, atau endpoint yang diminta tidak ditemukan, atau tidak dimiliki oleh merchant Anda.

409 Conflict

Operasi bertabrakan dengan resource yang ada, seperti reference ID duplikat atau idempotency key tabrakan dengan payload berbeda.

429 Too Many Requests

Terlalu banyak permintaan dalam waktu singkat (Rate Limiting). Silakan tunggu sebelum mencoba kembali.

Idempotency & Rate Limits

Idempotency

Saat membuat invoice, disarankan mengirimkan header Idempotency-Key (maksimal 100 karakter, berlaku per merchant). Jika Anda mengalami masalah koneksi jaringan dan harus mencoba kembali request yang sama, server akan mengenali key tersebut dan mengembalikan respons yang sama persis dengan percobaan pertama alih-alih membuat invoice ganda.

Idempotency-Key: ORDER-10001

Rate Limits

API menerapkan mekanisme pembatasan untuk menjaga keandalan layanan: /api/v1 dibatasi 300 request/menit per API key, /api/public 120 request/menit, dan /api/auth 60 request per 15 menit. Gunakan request API secara wajar dan hindari polling status secara agresif—gunakan webhook sebagai gantinya. Jika Anda menerima status kode 429 Too Many Requests, hentikan pengiriman request dan gunakan algoritma exponential backoff (contoh: 1s → 2s → 4s → 8s).

Implementation Examples

Contoh berikut mengilustrasikan cara memanggil endpoint Create Invoice dari server-side / backend merchant menggunakan berbagai bahasa pemrograman.

Node.js / TypeScript

const response = await fetch("/invoices", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": process.env.PAYQRIS_API_KEY,
    "Idempotency-Key": order.id,
  },
  body: JSON.stringify({
    amount: order.total,
    reference_id: order.id,
    callback_url: "https://merchant.example/webhook",
  }),
});

const result = await response.json();
if (!response.ok) throw new Error(result.error || "Request failed");

PHP (cURL)

$payload = json_encode([
    'amount' => 20000,
    'reference_id' => 'ORDER-10001',
    'callback_url' => 'https://merchant.example/webhook',
]);

$ch = curl_init('/invoices');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-API-Key: ' . getenv('PAYQRIS_API_KEY'),
        'Idempotency-Key: ORDER-10001',
    ],
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);

Python (requests)

import requests

response = requests.post(
    "/invoices",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Idempotency-Key": "ORDER-10001",
    },
    json={
        "amount": 20000,
        "reference_id": "ORDER-10001",
        "callback_url": "https://merchant.example/webhook",
    },
    timeout=15,
)
response.raise_for_status()
payment = response.json()["data"]