TUTORIAL DEVELOPER

Webhook Pembayaran QRIS dan Cara Cek Status Bayar Otomatis

Tim TemanQRISDiterbitkan 17 Oktober 2026 · 6 menit baca
ringkas
Jawaban singkat

Webhook pembayaran QRIS adalah pesan otomatis yang dikirim server penyedia QRIS ke server kamu setiap kali status pembayaran berubah, jadi sistem toko tidak perlu terus bertanya. Di TemanQRIS, webhook dikirim sebagai HTTP POST bertanda tangan HMAC-SHA256 untuk event payment.awaiting_confirmation dan payment.confirmed, dan dicoba ulang sampai 3 kali kalau server kamu gagal membalas.

Webhook, polling, dan callback URL: apa bedanya

Tiga istilah ini sering tertukar, padahal fungsinya beda jauh. Kalau salah pilih, toko bisa mengirim barang untuk pesanan yang belum dibayar.

CaraSiapa yang memulaiBisa jadi bukti bayar?Catatan
WebhookServer TemanQRIS mengirim ke server kamuYa, kalau tanda tangan validReal time, server kamu cukup menunggu
Polling (GET /api/qris/orders/:orderId)Server kamu bertanya berkalaYa, karena datanya dari API dengan API keyAda jeda antara pembayaran dan saat kamu bertanya
Callback URLBrowser pembeli diarahkan ke halaman kamuTidakHanya untuk pengalaman pengguna, siapa pun bisa membuka URL-nya

Pola yang kami sarankan: webhook sebagai sumber utama, polling ringan sebagai cadangan, dan callback URL murni untuk menampilkan halaman terima kasih.

Event webhook pembayaran QRIS di TemanQRIS

Ada dua event, dan keduanya dikirim ke webhook_url yang kamu isi saat membuat pembayaran.

  • payment.awaiting_confirmation: pembeli menekan tombol "Sudah Bayar" di halaman pembayaran atau widget. Ini pemberitahuan dari pembeli, belum bukti dana masuk.
  • payment.confirmed: pesanan sudah berstatus paid. Di mode reguler, ini terjadi setelah kamu mengonfirmasi lewat dashboard atau POST /api/qris/orders/:orderId/verify. Di mode Automatic Payment Gateway, server TemanQRIS sendiri yang mencocokkan pembayaran lalu mengirim event ini.

Jadi, seberapa "otomatis" cek status bayarnya tergantung mode. Di mode reguler, uang masuk langsung ke rekening QRIS kamu dan TemanQRIS tidak bisa melihat mutasinya, sehingga konfirmasi akhir tetap dari merchant. Yang otomatis adalah penyampaian statusnya ke sistem kamu. Di mode gateway (butuh KYC, maksimal Rp1.000.000 per transaksi), konfirmasinya juga otomatis.

Isi payload dan header webhook

Webhook dikirim sebagai POST dengan body JSON. Contoh untuk pesanan yang sudah lunas:

{
              "event": "payment.confirmed",
              "idempotency_key": "6f1c2a9e-3b7d-4e55-9a10-2c8f0d4b7e21",
              "timestamp": "2026-10-17T08:15:02.000Z",
              "data": {
                "order_id": "INV-20261017-0042",
                "link_code": "AB12CD34EF56",
                "amount": 125000,
                "description": "Pesanan INV-20261017-0042",
                "status": "paid",
                "paid_at": "2026-10-17T08:14:58.000Z",
                "created_at": "2026-10-17T07:58:11.000Z"
              }
            }

Untuk pembayaran mode gateway, objek data berisi field tambahan: payment_mode, payment_attempt_id, base_amount, fee_amount, unique_code, total_amount, provider_fee_amount, net_amount, dan confirmed_by. Nilai net_amount adalah jumlah yang masuk ke saldo kamu, yaitu base_amount + fee_amount - provider_fee_amount.

Header yang ikut dikirim:

  • X-TemanQRIS-Signature: tanda tangan HMAC-SHA256 atas raw body, formatnya sha256=<hex>.
  • X-TemanQRIS-Event: nama event.
  • X-TemanQRIS-Idempotency-Key: kunci unik pengiriman, sama dengan idempotency_key di body.
  • X-TemanQRIS-Retry: hanya ada di pengiriman ulang, berisi nomor percobaan ulang.

Cara menyiapkan webhook pembayaran QRIS

  1. Buat webhook secret di dashboard. Tanpa secret, webhook tidak dikirim sama sekali. Secret berupa 64 karakter heksadesimal, simpan di environment variable server kamu.
  2. Siapkan endpoint publik HTTPS. URL yang mengarah ke localhost, IP privat, atau link-local ditolak. Di production wajib HTTPS.
  3. Kirim webhook_url per pembayaran. Isi di body POST /api/qris/payment-link, POST /api/qris/generate, atau POST /api/payment-gateway/payments. Kalau pakai widget, gunakan atribut data-webhook.
  4. Verifikasi tanda tangan dari raw body. Hitung HMAC-SHA256 atas teks body persis seperti yang diterima, lalu bandingkan dengan perbandingan waktu-konstan.
  5. Cegah proses ganda. Simpan idempotency_key yang sudah diproses dan abaikan kalau datang lagi.
  6. Balas cepat dengan status 2xx. Batas waktu tunggu 10 detik. Simpan event, balas, lalu proses pekerjaan berat di background.

Contoh penerima webhook di Node.js:

const crypto = require('crypto');
            const express = require('express');
            const app = express();

            app.post('/webhooks/temanqris', express.raw({ type: 'application/json' }), async (req, res) => {
              const expected = 'sha256=' + crypto
                .createHmac('sha256', process.env.TEMANQRIS_WEBHOOK_SECRET)
                .update(req.body)
                .digest('hex');
              const got = req.get('X-TemanQRIS-Signature') || '';
              if (got.length !== expected.length ||
                  !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
                return res.status(401).end();
              }

              const event = JSON.parse(req.body.toString('utf8'));
              if (await sudahDiproses(event.idempotency_key)) return res.status(200).end();

              await simpanEvent(event); // proses lanjutan di worker/background
              res.status(204).end();
            });

Versi PHP dengan hash_hmac dan hash_equals, plus alur lengkap dari checkout sampai cek status, ada di panduan integrasi API QRIS di website.

Retry: apa yang terjadi kalau server kamu sedang down

Kalau server kamu membalas selain 2xx, timeout, atau tidak bisa dihubungi, TemanQRIS mencoba lagi sampai 3 kali dengan jeda yang makin panjang: sekitar 30 detik, 2 menit, lalu 10 menit setelah kegagalan sebelumnya. Total ada 4 percobaan. Setiap pengiriman ulang memakai payload dan idempotency_key yang sama, makanya deduplikasi di langkah 5 itu wajib.

Dua hal kecil yang sering bikin webhook dianggap gagal:

  • Redirect. Pengirim tidak mengikuti redirect, jadi balasan 301 atau 302 dihitung gagal. Ini sering terjadi kalau URL tanpa garis miring di akhir diarahkan ke versi dengan garis miring, atau HTTP diarahkan ke HTTPS.
  • Firewall atau proteksi bot. Kalau endpoint kamu ada di belakang proteksi yang menantang request tanpa browser, webhook tidak akan lolos. Kecualikan path webhook dari aturan itu dan andalkan verifikasi tanda tangan.

Cara cek status bayar kalau webhook belum masuk

Webhook bisa terlambat atau gagal, jadi siapkan jalur cadangan dengan GET /api/qris/orders/:orderId memakai header X-API-Key. Respons-nya memuat status, is_paid, is_expired, paid_at, dan expires_at.

Cek status order tidak memotong kuota harian (kuota 50 request di Free dan 1.000 di Premium dihitung dari endpoint yang membuat atau mengubah data, seperti generate, payment link, dan verify). Meski begitu, polling tetap sebaiknya hemat, demi server kamu sendiri dan supaya log tidak penuh. Yang kami sarankan:

  • Polling hanya saat pembeli masih membuka halaman menunggu, misalnya setiap 20 sampai 30 detik.
  • Berhenti begitu status paid, expired, atau cancelled.
  • Jalankan job rekonsiliasi harian untuk pesanan yang masih awaiting_confirmation, supaya tidak ada yang tertinggal.

Referensi lengkap endpoint dan contoh respons ada di dokumentasi API QRIS.

Cara menguji webhook tanpa transaksi sungguhan

Kabar baiknya, kamu bisa menguji kedua event tanpa memindahkan uang sungguhan. Caranya:

  1. Jalankan endpoint webhook di komputer kamu, lalu buka akses publiknya lewat tunnel seperti ngrok atau Cloudflare Tunnel. Pakai URL HTTPS yang diberikan tunnel sebagai webhook_url.
  2. Buat link pembayaran dengan nominal kecil dan Order ID khusus uji coba, misalnya TES-WEBHOOK-01.
  3. Buka link tersebut di browser dan klik "Sudah Bayar". Server kamu akan menerima payment.awaiting_confirmation.
  4. Tandai order itu lunas lewat dashboard atau endpoint verify. Server kamu akan menerima payment.confirmed.
  5. Matikan endpoint sebentar lalu ulangi dengan order baru untuk melihat perilaku retry dan memastikan deduplikasi berjalan.

Ingat, membuat link dan verify ikut memotong kuota harian, jadi rencanakan ujinya. Setelah selesai, batalkan order uji coba supaya laporan tidak tercampur. Panduan langkah per langkah dengan tangkapan layar dashboard ada di tutorial TemanQRIS, dan kalau kamu belum punya alur pembayaran sama sekali, mulai dari cara terima pembayaran QRIS untuk toko online kecil.

Checklist sebelum go-live

  • Webhook secret sudah dibuat dan tersimpan di server, bukan di kode.
  • Endpoint HTTPS bisa diakses publik dan tidak redirect.
  • Tanda tangan diverifikasi dari raw body dengan perbandingan waktu-konstan.
  • idempotency_key disimpan dan dicek sebelum memproses.
  • Pesanan hanya dianggap lunas dari payment.confirmed atau status paid dari API, bukan dari callback URL.
  • Ada polling cadangan dengan jeda wajar dan job rekonsiliasi harian.
  • Log mencatat event, order_id, dan hasil verifikasi, tanpa menyimpan secret.

Pertanyaan yang Sering Diajukan (FAQ)

Apa beda webhook dan callback URL?

Webhook dikirim dari server TemanQRIS ke server kamu dan bisa diverifikasi lewat tanda tangan HMAC. Callback URL hanya mengarahkan browser pembeli ke halaman kamu, jadi tidak boleh dipakai sebagai bukti pembayaran.

Apakah webhook TemanQRIS mengonfirmasi pembayaran secara otomatis?

Tergantung mode. Di mode reguler, event payment.confirmed dikirim setelah merchant mengonfirmasi lunas. Di mode Automatic Payment Gateway, server TemanQRIS mencocokkan pembayaran sendiri lalu mengirim event tersebut.

Berapa kali webhook dicoba ulang kalau gagal?

Sampai 3 kali setelah percobaan pertama, dengan jeda sekitar 30 detik, 2 menit, dan 10 menit. Semua pengiriman ulang membawa idempotency_key yang sama.

Kenapa webhook pembayaran QRIS tidak terkirim?

Biasanya karena webhook secret belum dibuat di dashboard, URL mengarah ke alamat privat atau tidak HTTPS, endpoint membalas redirect, atau proteksi bot memblokir request dari server.