Lewati ke konten
REST API · v1

Dokumentasi API QRIS

Semua yang Anda butuhkan untuk mengintegrasikan QRIS dinamis, deteksi pembayaran otomatis, dan webhook ke website, aplikasi, atau chatbot.

Base URLhttps://api.apiqris.com/v1

Pengantar

API QRIS APIQris adalah REST API berbasis JSON untuk membuat QRIS dinamis dari QRIS milik Anda sendiri dan mengetahui kapan pembayaran masuk. Dana pembeli langsung masuk ke akun merchant QRIS Anda, APIQris hanya mengurus data tagihan, pencocokan, dan webhook.

Base URLhttps://api.apiqris.com/v1
Formatapplication/json, UTF-8
AutentikasiBearer API key
Zona waktuISO 8601, Asia/Jakarta (+07:00)
Mata uangIDR, bilangan bulat rupiah
Versiv1

Mulai cepat

  1. Daftar dan upload QRISBuat akun, lalu unggah gambar QRIS merchant Anda.
  2. Pasang aplikasi pendampingPairing HP Android yang menerima notifikasi dana masuk dari aplikasi merchant.
  3. Ambil API keyMenu Developer: salin AQ_TEST_ untuk uji dan secret webhook.
  4. Buat tagihan pertamaPanggil POST /invoices dan tampilkan qr_image_url.
  5. Terima webhookVerifikasi tanda tangan, tandai pesanan lunas, balas 200.
Tagihan pertama Anda
curl -X POST https://api.apiqris.com/v1/invoices \
  -H "Authorization: Bearer AQ_LIVE_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"amount":50000,"order_id":"INV-1024"}'
<?php
$ch = curl_init('https://api.apiqris.com/v1/invoices');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount'   => 50000,
        'order_id' => 'INV-1024',
    ]),
]);
$qris = json_decode(curl_exec($ch), true);
echo $qris['qr_image_url']; // tampilkan ke pembeli
use Illuminate\Support\Facades\Http;

$qris = Http::withToken(config('services.apiqris.key'))
    ->post('https://api.apiqris.com/v1/invoices', [
        'amount'   => $order->total,
        'order_id' => $order->number,
    ])
    ->throw()
    ->json();

return view('checkout.qris', ['qris' => $qris]);
const res = await fetch('https://api.apiqris.com/v1/invoices', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ amount: 50000, order_id: 'INV-1024' }),
});
const qris = await res.json();
console.log(qris.total_amount, qris.qr_image_url);
import os, requests

r = requests.post(
    "https://api.apiqris.com/v1/invoices",
    headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
    json={"amount": 50000, "order_id": "INV-1024"},
    timeout=15,
)
r.raise_for_status()
qris = r.json()
print(qris["total_amount"], qris["qr_image_url"])
body := strings.NewReader(`{"amount":50000,"order_id":"INV-1024"}`)
req, _ := http.NewRequest("POST", "https://api.apiqris.com/v1/invoices", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("APIQRIS_KEY"))
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()

Autentikasi

Setiap request wajib memakai HTTPS dan menyertakan API key di header Authorization. Request tanpa key yang valid akan ditolak dengan 401 UNAUTHORIZED.

Header request
Authorization: Bearer AQ_LIVE_xxxxxxxxxxxxxxxx
Content-Type: application/json
Accept: application/json
Jaga kerahasiaan API keyJangan menaruh key di kode frontend, aplikasi mobile, atau repositori publik. Bila bocor, rotasi key dari dashboard; key lama langsung tidak berlaku.

Mode uji & live

ModeAwalan keyKegunaan
UjiAQ_TEST_Development. Tagihan bisa disimulasikan lunas, tidak ada uang sungguhan.
LiveAQ_LIVE_Produksi. Lunas hanya bila pembayaran nyata terdeteksi.

Objek tagihan

Semua endpoint tagihan mengembalikan objek berikut.

FieldTipeKeterangan
idstringID tagihan APIQris, diawali qr_.
order_idstringID pesanan dari sistem Anda.
amountintegerNominal asli yang diminta.
unique_codeintegerKode unik yang ditambahkan.
total_amountintegerTotal yang harus dibayar pembeli (amount + unique_code).
statusstringpending, paid, expired, atau cancelled.
qris_stringstringString QRIS dinamis untuk dirender sendiri.
qr_image_urlstringURL gambar QR PNG siap tampil.
expires_atstringBatas waktu pembayaran (ISO 8601, WIB).
paid_atstring|nullWaktu lunas, null bila belum dibayar.

Status tagihan

pending

Menunggu pembayaran.

paid

Pembayaran terdeteksi dan cocok.

expired

Lewat masa berlaku tanpa pembayaran.

cancelled

Dibatalkan lewat API atau dashboard.

pending → paid  atau  pending → expired / cancelled. Status final tidak berubah lagi.

Kode unik

Notifikasi dana masuk hanya berisi nominal. Agar setiap pembayaran bisa dikenali, total tagihan ditambah kode unik kecil sehingga tidak ada dua tagihan pending di QRIS yang sama dengan total persis sama. Selalu tampilkan total_amount kepada pembeli, bukan amount.

ContohDua pesanan Rp50.000 pada waktu bersamaan menjadi Rp50.123 dan Rp50.481. Rentang dan mode kode unik (tambah/kurang) bisa diatur di dashboard.

Idempotensi

Pembuatan tagihan idempoten berdasarkan order_id. Jika koneksi terputus dan Anda mengulang request dengan order_id yang sama selama tagihan masih pending, APIQris mengembalikan tagihan yang sudah ada, bukan membuat yang baru. Aman untuk retry otomatis.

POST/invoices

Buat tagihan QRIS dinamis

Membuat QRIS dinamis dari QRIS Anda dengan nominal plus kode unik. Request dengan order_id yang sama saat tagihan masih pending mengembalikan tagihan yang sama (idempoten).

Body request

NamaTipeWajibKeterangan
amountintegerwajibNominal dalam rupiah, minimal 1.000.
order_idstringwajibID pesanan unik dari sistem Anda, maks 64 karakter.
callback_urlstringopsionalURL webhook khusus tagihan ini. Default memakai URL di pengaturan toko.
expired_minutesintegeropsionalMasa berlaku 5 sampai 1440 menit. Default 30.
qris_account_idstringopsionalPilih QRIS tertentu bila akun punya lebih dari satu.
descriptionstringopsionalKeterangan tagihan, tampil di halaman bayar.
customer_whatsappstringopsionalNomor pembeli untuk notifikasi WhatsApp (format 62xxx).
redirect_urlstringopsionalHalaman tujuan setelah lunas bila memakai halaman bayar.

Contoh request

POST /invoices
curl -X POST https://api.apiqris.com/v1/invoices \
  -H "Authorization: Bearer $APIQRIS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":50000,"order_id":"INV-1024","expired_minutes":30}'
const res = await fetch('https://api.apiqris.com/v1/invoices', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({"amount":50000,"order_id":"INV-1024","expired_minutes":30}),
});
const data = await res.json();
<?php
$ch = curl_init('https://api.apiqris.com/v1/invoices');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => 50000,
        'order_id' => 'INV-1024',
        'expired_minutes' => 30,
    ]),
]);
$data = json_decode(curl_exec($ch), true);
import os, requests

r = requests.post(
    "https://api.apiqris.com/v1/invoices",
    headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
    json={"amount": 50000, "order_id": "INV-1024", "expired_minutes": 30},
    timeout=15,
)
data = r.json()
Respons
{
  "id": "qr_9KX2T7",
  "order_id": "INV-1024",
  "amount": 50000,
  "unique_code": 123,
  "total_amount": 50123,
  "status": "pending",
  "qris_string": "00020101021226...6304A1B2",
  "qr_image_url": "https://api.apiqris.com/v1/qr/qr_9KX2T7.png",
  "expires_at": "2026-10-17T10:15:00+07:00"
}
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "amount minimal 1000"
  }
}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "API key tidak valid atau sudah dicabut"
  }
}
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Terlalu banyak request, coba lagi setelah 1 detik"
  }
}
GET/invoices/{id}

Ambil detail tagihan

Mengambil status terbaru tagihan. Parameter id bisa berupa id tagihan APIQris atau order_id milik Anda.

Path parameter

NamaTipeWajibKeterangan
idstringwajibid tagihan (qr_xxx) atau order_id.
Gunakan webhook sebagai sumber utama. Polling endpoint ini di halaman pembeli boleh dengan jeda minimal 2 detik.

Contoh request

GET /invoices/{id}
curl -X GET https://api.apiqris.com/v1/invoices/INV-1024 \
  -H "Authorization: Bearer $APIQRIS_KEY"
const res = await fetch('https://api.apiqris.com/v1/invoices/INV-1024', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
  },
});
const data = await res.json();
<?php
$ch = curl_init('https://api.apiqris.com/v1/invoices/INV-1024');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
        'Content-Type: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true);
import os, requests

r = requests.get(
    "https://api.apiqris.com/v1/invoices/INV-1024",
    headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
    timeout=15,
)
data = r.json()
Respons
{
  "id": "qr_9KX2T7",
  "order_id": "INV-1024",
  "amount": 50000,
  "unique_code": 123,
  "total_amount": 50123,
  "status": "paid",
  "qris_string": "00020101021226...6304A1B2",
  "qr_image_url": "https://api.apiqris.com/v1/qr/qr_9KX2T7.png",
  "expires_at": "2026-10-17T10:15:00+07:00",
  "paid_at": "2026-10-17T10:03:12+07:00"
}
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Tagihan tidak ditemukan"
  }
}
POST/invoices/{id}/cancel

Batalkan tagihan

Membatalkan tagihan yang masih pending. Event invoice.cancelled dikirim ke webhook.

Path parameter

NamaTipeWajibKeterangan
idstringwajibid tagihan atau order_id.

Contoh request

POST /invoices/{id}/cancel
curl -X POST https://api.apiqris.com/v1/invoices/INV-1024/cancel \
  -H "Authorization: Bearer $APIQRIS_KEY"
const res = await fetch('https://api.apiqris.com/v1/invoices/INV-1024/cancel', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
  },
});
const data = await res.json();
<?php
$ch = curl_init('https://api.apiqris.com/v1/invoices/INV-1024/cancel');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
        'Content-Type: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true);
import os, requests

r = requests.post(
    "https://api.apiqris.com/v1/invoices/INV-1024/cancel",
    headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
    timeout=15,
)
data = r.json()
Respons
{
  "id": "qr_9KX2T7",
  "order_id": "INV-1024",
  "amount": 50000,
  "unique_code": 123,
  "total_amount": 50123,
  "status": "cancelled",
  "qris_string": "00020101021226...6304A1B2",
  "qr_image_url": "https://api.apiqris.com/v1/qr/qr_9KX2T7.png",
  "expires_at": "2026-10-17T10:15:00+07:00"
}
{
  "error": {
    "code": "INVALID_STATE",
    "message": "Tagihan sudah lunas dan tidak bisa dibatalkan"
  }
}
POST/invoices/{id}/simulate

Simulasikan pembayaran

Hanya untuk API key mode uji (AQ_TEST_). Menandai tagihan lunas dan mengirim webhook seperti pembayaran sungguhan.

Path parameter

NamaTipeWajibKeterangan
idstringwajibid tagihan atau order_id.

Contoh request

POST /invoices/{id}/simulate
curl -X POST https://api.apiqris.com/v1/invoices/INV-1024/simulate \
  -H "Authorization: Bearer $APIQRIS_KEY"
const res = await fetch('https://api.apiqris.com/v1/invoices/INV-1024/simulate', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
  },
});
const data = await res.json();
<?php
$ch = curl_init('https://api.apiqris.com/v1/invoices/INV-1024/simulate');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
        'Content-Type: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true);
import os, requests

r = requests.post(
    "https://api.apiqris.com/v1/invoices/INV-1024/simulate",
    headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
    timeout=15,
)
data = r.json()
Respons
{
  "id": "qr_9KX2T7",
  "order_id": "INV-1024",
  "amount": 50000,
  "unique_code": 123,
  "total_amount": 50123,
  "status": "paid",
  "qris_string": "00020101021226...6304A1B2",
  "qr_image_url": "https://api.apiqris.com/v1/qr/qr_9KX2T7.png",
  "expires_at": "2026-10-17T10:15:00+07:00",
  "paid_at": "2026-10-17T10:03:12+07:00"
}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Simulasi hanya tersedia untuk API key mode uji"
  }
}
GET/qris-accounts

Daftar QRIS terhubung

Menampilkan QRIS yang terhubung ke akun beserta status HP pendeteksinya.

Contoh request

GET /qris-accounts
curl -X GET https://api.apiqris.com/v1/qris-accounts \
  -H "Authorization: Bearer $APIQRIS_KEY"
const res = await fetch('https://api.apiqris.com/v1/qris-accounts', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
  },
});
const data = await res.json();
<?php
$ch = curl_init('https://api.apiqris.com/v1/qris-accounts');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
        'Content-Type: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true);
import os, requests

r = requests.get(
    "https://api.apiqris.com/v1/qris-accounts",
    headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
    timeout=15,
)
data = r.json()
Respons
{
  "data": [
    {
      "id": "qa_7Hf2",
      "name": "GoPay Merchant Toko",
      "merchant_name": "TOKO ANDA",
      "nmid": "ID10xxxxxxxxx",
      "device_online": true,
      "is_default": true
    }
  ]
}
GET/me

Info akun

Mengecek API key dan melihat ringkasan akun, termasuk mode key (live/test).

Contoh request

GET /me
curl -X GET https://api.apiqris.com/v1/me \
  -H "Authorization: Bearer $APIQRIS_KEY"
const res = await fetch('https://api.apiqris.com/v1/me', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
  },
});
const data = await res.json();
<?php
$ch = curl_init('https://api.apiqris.com/v1/me');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
        'Content-Type: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true);
import os, requests

r = requests.get(
    "https://api.apiqris.com/v1/me",
    headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
    timeout=15,
)
data = r.json()
Respons
{
  "id": "usr_91aa",
  "name": "Toko Anda",
  "mode": "live",
  "pending_invoices": 3,
  "webhook_url": "https://tokoanda.com/webhook/qris"
}

Event webhook

APIQris mengirim POST JSON ke callback URL setiap kali status tagihan berubah. Callback URL diatur per toko di dashboard atau per tagihan lewat callback_url.

EventKapan dikirim
invoice.paidTagihan lunas. Proses pesanan di sini.
invoice.expiredTagihan kedaluwarsa tanpa pembayaran.
invoice.cancelledTagihan dibatalkan.
pingUji koneksi dari dashboard.

Contoh payload

Request dari APIQris ke server Anda
POST /webhook/qris HTTP/1.1
Host: tokoanda.com
Content-Type: application/json
User-Agent: APIQris-Webhook/1.0
X-APIQris-Event: invoice.paid
X-APIQris-Signature: t=1760670192,v1=5f2b8c...e9d1

{
  "event": "invoice.paid",
  "id": "qr_9KX2T7",
  "order_id": "INV-1024",
  "amount": 50000,
  "unique_code": 123,
  "total_amount": 50123,
  "status": "paid",
  "paid_at": "2026-10-17T10:03:12+07:00",
  "created_at": "2026-10-17T10:00:00+07:00"
}

Verifikasi tanda tangan

Header X-APIQris-Signature berformat t=<timestamp>,v1=<hex>. Hitung ulang HMAC SHA-256 dari body mentah (sebelum di-parse) dan tolak bila tidak cocok atau timestamp lebih dari 5 menit.

Algoritma
signed_payload = timestamp + "." + raw_body
expected       = HEX( HMAC_SHA256(webhook_secret, signed_payload) )
valid          = constant_time_equal(expected, v1) AND |now - timestamp| <= 300
Webhook handler
<?php
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_APIQRIS_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, getenv('APIQRIS_SECRET'));

if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
    http_response_code(401);
    exit;
}
$event = json_decode($body, true);
if ($event['event'] === 'invoice.paid') {
    tandai_lunas($event['order_id']); // fungsi milik aplikasi Anda
}
http_response_code(200);
Route::post('/webhook/qris', function (Request $request) {
    parse_str(str_replace(',', '&', $request->header('X-APIQris-Signature')), $sig);
    $expected = hash_hmac('sha256', $sig['t'].'.'.$request->getContent(), config('services.apiqris.secret'));
    abort_unless(hash_equals($expected, $sig['v1'] ?? ''), 401);

    if ($request->input('event') === 'invoice.paid') {
        Order::where('number', $request->input('order_id'))->update(['status' => 'paid']);
    }
    return response()->noContent();
});
import crypto from 'node:crypto';
import express from 'express';

const app = express();
app.post('/webhook/qris', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = Object.fromEntries(req.get('X-APIQris-Signature').split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', process.env.APIQRIS_SECRET)
    .update(`${sig.t}.${req.body}`).digest('hex');
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1 ?? ''))) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  if (event.event === 'invoice.paid') markPaid(event.order_id);
  res.sendStatus(200);
});
import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/webhook/qris")
def webhook():
    sig = dict(p.split("=", 1) for p in request.headers["X-APIQris-Signature"].split(","))
    msg = f"{sig['t']}.".encode() + request.get_data()
    expected = hmac.new(os.environ["APIQRIS_SECRET"].encode(), msg, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig.get("v1", "")):
        abort(401)
    event = request.get_json()
    if event["event"] == "invoice.paid":
        mark_paid(event["order_id"])
    return "", 200
func webhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    sig := map[string]string{}
    for _, p := range strings.Split(r.Header.Get("X-APIQris-Signature"), ",") {
        if k, v, ok := strings.Cut(p, "="); ok {
            sig[k] = v
        }
    }
    mac := hmac.New(sha256.New, []byte(os.Getenv("APIQRIS_SECRET")))
    mac.Write([]byte(sig["t"] + "." + string(body)))
    if !hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(sig["v1"])) {
        http.Error(w, "invalid signature", http.StatusUnauthorized)
        return
    }
    w.WriteHeader(http.StatusOK)
}
# Header yang dikirim APIQris ke callback_url Anda
X-APIQris-Signature: t=1760670000,v1=5f2b...e9
Content-Type: application/json

{"event":"invoice.paid","id":"qr_9KX2T7","order_id":"INV-1024","total_amount":50123,"status":"paid"}

Kirim ulang

Webhook dianggap berhasil bila server Anda membalas status 2xx dalam 10 detik. Bila gagal, APIQris mencoba lagi dengan jeda berikut:

#11 menit
#25 menit
#315 menit
#41 jam
#56 jam
#624 jam

Anda juga bisa melihat log respons dan menekan "kirim ulang" dari dashboard. Karena webhook bisa terkirim lebih dari sekali, pastikan proses Anda idempoten.

Kode error

Semua error memakai status HTTP yang sesuai dan body JSON berikut:

{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "amount minimal 1000"
  }
}
HTTPKodeArti & solusi
400INVALID_REQUESTParameter salah atau kurang. Baca field message.
401UNAUTHORIZEDAPI key tidak valid, salah format, atau sudah dicabut.
403FORBIDDENAksi tidak diizinkan untuk key ini (misalnya simulasi dengan key live).
404NOT_FOUNDTagihan atau sumber daya tidak ditemukan.
409INVALID_STATEStatus tagihan tidak memungkinkan aksi tersebut.
409NO_UNIQUE_SLOTKode unik untuk nominal ini sedang penuh. Coba lagi atau ubah nominal.
429RATE_LIMITEDTerlalu banyak request. Ikuti header Retry-After.
500INTERNAL_ERRORKesalahan di sisi kami. Aman untuk diulang dengan order_id yang sama.

Batas request

JenisBatas
Buat tagihan10 request/detik per akun
Endpoint lain120 request/menit per API key
Tagihan pendingBatas wajar per akun (lihat dashboard)

Saat melewati batas, API membalas 429 RATE_LIMITED dengan header Retry-After (detik). Ulangi request setelah jeda tersebut.

Halaman bayar & redirect

Tidak ingin mendesain halaman QR sendiri? Arahkan pembeli ke halaman bayar APIQris yang menampilkan QR, total, dan hitung mundur. Setelah lunas, pembeli dikembalikan ke redirect_url.

Alur redirect
// 1. Buat tagihan dengan redirect_url
const qris = await createInvoice({ amount: 50000, order_id: 'INV-1024', redirect_url: 'https://tokoanda.com/selesai' });

// 2. Arahkan pembeli ke halaman bayar APIQris
res.redirect(`https://app.apiqris.com/pay/${qris.id}`);

// 3. Setelah lunas pembeli kembali ke redirect_url,
//    tetap andalkan webhook untuk menandai pesanan lunas.

Checklist go-live

  • Ganti API key uji (AQ_TEST_) dengan key live (AQ_LIVE_)
  • Callback URL memakai HTTPS dan membalas 2xx dalam 10 detik
  • Tanda tangan webhook diverifikasi sebelum memproses pesanan
  • Proses webhook idempoten berdasarkan order_id
  • API key disimpan di server, tidak di frontend atau aplikasi mobile
  • HP pendamping online dan uji bayar Rp1.000 berhasil

Changelog

v1
Oktober 2026

Rilis awal: tagihan QRIS dinamis, kode unik, webhook HMAC SHA-256 dengan kirim ulang, mode uji, multi QRIS.

Panduan per bahasa & platform

Belum yakin? Baca cara kerja dan FAQ, atau kembali ke halaman utama API QRIS gratis.