Lewati ke konten
API QRIS per Bahasa

Integrasi API QRIS dengan Node.js

Tanpa dependensi tambahan untuk membuat tagihan. Cocok untuk Express, Fastify, Next.js, dan bot.

Halaman ini bagian dari layanan API QRIS gratis APIQris: pakai QRIS milik Anda sendiri, tanpa potongan dari kami, dana langsung ke rekening Anda.

  • 0% fee APIQris
  • Dana langsung ke Anda
  • Webhook HMAC

Jawaban singkatDi Node.js 18 ke atas, panggil API QRIS dengan fetch bawaan: POST ke /invoices dengan header Authorization Bearer. Untuk webhook, terima body mentah (express.raw), hitung HMAC SHA-256 dengan modul crypto, dan bandingkan memakai timingSafeEqual.

Ingin tahu gambaran besarnya dulu? Baca juga pengertian QRIS API dan bagaimana API QRIS gratis APIQris menghubungkan QRIS Anda ke website, aplikasi, dan chatbot.

Rp0potongan dari APIQris
24/7konfirmasi otomatis
100%dana ke rekening Anda
Masalah

Tanpa API QRIS Node.js, ini yang biasa terjadi

Menulis generator QRIS dari nol

Parsing TLV, urutan tag, dan CRC-16 rawan salah dan memakan waktu.

Polling mutasi bank

Scraping internet banking rapuh, sering diblokir, dan butuh menyimpan password.

Webhook tanpa tanda tangan

Endpoint callback yang tidak diverifikasi bisa dipalsukan siapa saja.

Sulit diuji

Tanpa sandbox, developer harus bayar sungguhan setiap kali mengetes alur checkout.

Solusinya: API QRIS Node.js dari APIQris mengotomatiskan semuanya, tanpa mengganti QRIS yang sudah Anda punya.

Panduan

Memahami API QRIS Node.js secara lengkap

01

Kenapa body mentah penting

Tanda tangan dihitung dari body persis seperti yang dikirim. Jika JSON di-parse lalu di-stringify ulang, urutan atau spasi bisa berubah dan verifikasi gagal. Karena itu gunakan express.raw pada route webhook.

02

Pola yang disarankan

Beberapa pola umum di proyek Node.js:

  • Next.js: route handler app/api/webhook/route.ts membaca await req.text()
  • Bot WhatsApp (Baileys/whatsapp-web.js): kirim gambar QR dari qr_image_url
  • Antrean BullMQ untuk memproses event lunas
  • Simpan secret di process.env, jangan di repo
03

TypeScript

Respons API berbentuk JSON sederhana sehingga mudah dibuatkan interface. Lihat struktur respons di halaman dokumentasi.

Cara kerja

Alur API QRIS Node.js dari scan sampai lunas

Dana dan data berjalan di jalur berbeda. Uang pembeli langsung ke QRIS Anda, APIQris hanya mengurus QRIS dinamis, pencocokan, dan webhook.

Infografis cara kerja API QRIS APIQris Dana pembeli mengalir dari pembeli ke penyedia QRIS lalu langsung ke rekening Anda. Jalur data terpisah: notifikasi dari HP merchant Anda dibaca APIQris lalu dikirim sebagai webhook ke website atau bot Anda. Rp Rp bayar dana langsung notifikasi kirim mutasi webhook buat tagihan → tampilkan QRIS dinamis ke pembeli Pembeli Scan & bayar Penyedia QRISAnda Bank / e-wallet Rekening Anda Langsung ke Anda HP MerchantAnda Notif dana masuk APIQris Cocokkan tagihan Website /Bot Anda Webhook lunas APIQris tidak menyentuh dana
Jalur dana: pembeli → penyedia QRIS → rekening Anda Jalur data: notifikasi → APIQris → webhook ke sistem Anda
  1. 1

    Daftar gratis

    Buat akun APIQris dengan email, tanpa biaya dan tanpa dokumen perusahaan.

  2. 2

    Upload QRIS Anda

    Unggah gambar QRIS merchant dari bank atau e-wallet Anda. Sistem membaca data QRIS secara otomatis.

  3. 3

    Pasang aplikasi pendamping

    Pasang aplikasi APIQris di HP Android yang menerima notifikasi dana masuk, lalu pindai kode pairing.

  4. 4

    Pakai API key

    Salin API key, buat tagihan QRIS dinamis dari website atau chatbot, dan terima webhook saat lunas.

Contoh skenario

Sebelum dan sesudah memakai API QRIS Node.js

Developer freelance yang membangun toko online untuk klien. Ilustrasi alur kerja, bukan testimoni pelanggan.

Sebelum

Setiap proyek dimulai dari nol: mencari gateway, mengurus dokumen klien, dan menulis ulang modul pembayaran.

Sesudah

Dengan API QRIS Node.js, satu modul yang sama dipakai di semua proyek. Klien cukup upload QRIS miliknya, dana langsung ke rekening klien.

Perbandingan

QRIS statis manual vs API QRIS Node.js vs payment gateway

AspekQRIS statis manualAPI QRIS Node.js (APIQris)Payment gateway
NominalDiketik pembeliTerkunci otomatisTerkunci otomatis
Konfirmasi bayarCek mutasi manualWebhook otomatisWebhook otomatis
Penerima danaRekening AndaRekening AndaRekening gateway
Potongan perantaraRp0Rp0±0,7% + biaya tarik
Bekerja 24 jamTergantung adminYaYa
Contoh kode

Buat tagihan dengan API QRIS Node.js

Kirim nominal dan order ID, terima QR dan total yang sudah ditambah kode unik.

Buat tagihan 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);
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]);
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()
Webhook

Verifikasi kabar lunas

Hitung HMAC SHA-256 dari timestamp dan body mentah, bandingkan secara constant-time.

Verifikasi webhook
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);
});
# 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"}
<?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 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)
}
Keamanan

Aman sejak baris kode pertama

HTTPS di semua endpoint

Data tagihan dan API key selalu terenkripsi saat dikirim.

API key bisa dirotasi

Kunci bocor? Buat kunci baru dari dashboard, kunci lama langsung mati.

Webhook HMAC SHA-256

Setiap callback ditandatangani agar server Anda bisa menolak yang palsu.

Dana tidak lewat kami

Tidak ada saldo yang bisa dibekukan, karena uang langsung ke QRIS Anda.

Sebelum go-live

Checklist API QRIS Node.js

  • QRIS merchant resmi sudah diupload dan terbaca benar
  • Aplikasi pendamping di HP Android berstatus online
  • Optimasi baterai dimatikan untuk aplikasi merchant & pendamping
  • Uji bayar Rp1.000 berhasil terdeteksi lunas
  • URL webhook memakai HTTPS dan membalas 200
  • Tanda tangan HMAC diverifikasi sebelum memproses pesanan
  • Proses webhook idempoten berdasarkan order_id
  • API key disimpan di server, tidak di frontend
Istilah

Glosarium singkat

QRIS
Quick Response Code Indonesian Standard, standar QR pembayaran nasional dari Bank Indonesia.
QRIS dinamis
QRIS yang membawa nominal untuk satu transaksi, berbeda dengan QRIS statis tanpa nominal.
NMID
National Merchant ID, nomor identitas merchant yang tertanam di QRIS Anda.
MDR
Merchant Discount Rate, biaya yang dipotong penyedia QRIS atas transaksi sesuai ketentuan.
Kode unik
Angka kecil yang ditambahkan ke nominal agar setiap tagihan bisa dibedakan.
Webhook
HTTP POST otomatis dari APIQris ke server Anda saat status tagihan berubah.
FAQ

Pertanyaan umum tentang API QRIS Node.js

Apakah butuh axios?

Tidak wajib. fetch bawaan Node.js 18+ sudah cukup.

Bisa dipakai di serverless?

Bisa, termasuk Vercel dan Cloudflare Workers, selama route webhook membaca body mentah.

Apakah dana dari API QRIS Node.js ditampung APIQris?

Tidak. Pembeli membayar ke QRIS milik Anda sendiri, sehingga dana langsung masuk ke rekening atau saldo merchant Anda.

Berapa biaya memakai APIQris?

Daftar gratis dan APIQris tidak memotong fee per transaksi. MDR dari penyedia QRIS Anda (bila ada) mengikuti ketentuan penyedia.

Apakah perlu HP khusus?

Cukup satu HP Android yang terpasang aplikasi merchant penerbit QRIS Anda dan aplikasi pendamping APIQris. HP lama pun bisa selama notifikasinya stabil.

Mulai API QRIS Node.js hari ini

Gratis, tanpa potongan per transaksi, dana langsung ke rekening Anda. Pelajari dulu API QRIS selengkapnya atau langsung daftar.