REST API · JSON · HTTPS

Dokumentasi API WaroengPay

Buat tagihan QRIS dari sistem tokomu, arahkan pembeli ke halaman bayar, lalu terima webhook saat pembayaran masuk. Semua endpoint memakai JSON dan API key milik akunmu. Data antar akun terpisah: kamu hanya bisa melihat tagihanmu sendiri.

Base URL
Untuk AI agent/llms.txt/docs.md

Kredensial

API key untuk memanggil API, dan secret untuk memverifikasi webhook yang kami kirim ke tokomu.

API key live (wp_live_), API key uji (wp_test_), dan secret webhook dibuat di dashboard setelah akunmu aktif.

Mulai cepat

Empat langkah dari nol sampai pembayaran pertama tercatat otomatis di sistem tokomu.

2
3
4

1. Buat API key

Buat API key di bagian Kredensial di atas. Simpan di environment variable server, misalnya WAROENGPAY_API_KEY. Jangan pernah menaruhnya di kode frontend atau aplikasi mobile.

Autentikasi

Setiap request API wajib membawa API key di header Authorization. Key wp_test_ untuk mode uji (lihat Sandbox).

Header
Authorization: Bearer wp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
API key disimpan terenkripsi dan tersembunyi di dashboard. Hanya kamu yang bisa menampilkannya lagi, dengan password akun. Curiga bocor? Buat ulang: key lama langsung mati.
Membuat ulang API key langsung mematikan key lama. Lakukan ini kalau key pernah bocor.
API key hanya untuk server-ke-server. Request dari browser tanpa key memakai sesi dashboard, bukan API key.
API key salah atau akun nonaktif dijawab 401 invalid_api_key.

Buat tagihan

POST/api/payments

Membuat tagihan QRIS dinamis dengan nominal unik. Balasan 201 berisi tagihan baru.

amountintegerwajib

Nominal dalam rupiah, Rp1.000 sampai Rp10.000.000. Kode unik ditambahkan otomatis.

referencestring

Nomor pesanan di sistemmu (maks 100 karakter). Ikut dikirim di webhook. Tidak unik: cocokkan pembayaran lewat id, bukan reference.

descriptionstring

Keterangan yang tampil di halaman bayar (maks 200 karakter).

callback_urlurl

Penerima webhook tagihan ini. Kosong = pakai URL webhook default akunmu (tagihan uji: kosong = tanpa webhook).

redirect_urlurl

Pembeli diarahkan ke sini 6 detik setelah pembayaran diterima.

expires_ininteger

Masa berlaku dalam menit, 5 sampai 60. Default 15.

Idempotency-Keyheader

Opsional. Kirim ulang request dengan key yang sama = tagihan yang sama (200), bukan tagihan baru. Pakai nomor pesanan.

curl -X POST https://waroengpay.com/api/payments \
-H "Authorization: Bearer $WAROENGPAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: INV-2041" \
-d '{
"amount": 50000,
"reference": "INV-2041",
"description": "Paket Premium 1 bulan",
"callback_url": "https://tokoku.id/webhook/waroengpay",
"redirect_url": "https://tokoku.id/pesanan/INV-2041"
}'
Respons 201
{
"id": "WRG-7K2M9QX4BD1PZ",
"reference": "INV-2041",
"description": "Paket Premium 1 bulan",
"status": "pending",
"base_amount": 50000,
"unique_code": 137,
"amount": 50137,
"qris": "000201010212265...6304A1B2",
"pay_url": "https://waroengpay.com/pay/WRG-7K2M9QX4BD1PZ",
"callback_url": "https://tokoku.id/webhook/waroengpay",
"redirect_url": "https://tokoku.id/pesanan/INV-2041",
"created_at": "2026-09-24T03:00:00.000Z",
"expires_at": "2026-09-24T03:15:00.000Z",
"paid_at": null,
"late": false,
"livemode": true,
"link_id": null,
"payment": null
}
Tampilkan amount (sudah termasuk kode unik) ke pembeli. Pembayaran dengan nominal berbeda tidak bisa dicocokkan otomatis.

Ambil tagihan

GET/api/payments/:id

Status terbaru satu tagihan, termasuk detail pembayaran dan riwayat webhook-nya.

curl https://waroengpay.com/api/payments/WRG-7K2M9QX4BD1PZ \
-H "Authorization: Bearer $WAROENGPAY_API_KEY"

Setelah lunas, objek payment berisi issuer (bank atau e-wallet pembayar), payer (nama yang sudah disamarkan bank), bca_ref (nomor referensi transaksi), dan transaction_at. Field webhooks berisi 20 pengiriman terakhir.

Struk gambar (PNG) untuk dikirim ke pembeli: GET /api/payments/:id/receipt.png. Hanya tagihan lunas, selain itu 409 not_paid.

Daftar tagihan

GET/api/payments

Tagihan milikmu, terbaru lebih dulu, dengan paginasi berbasis waktu.

statusstring

Saring: pending, paid, expired, atau cancelled.

from, toISO 8601

Rentang waktu. from ikut, to tidak ikut.

bystring

paid = rentang dan urutan memakai waktu bayar. Default waktu dibuat.

qstring

Cari di referensi, keterangan, ID, ref bank, nama pembayar, atau nominal persis.

issuerstring

Bank atau e-wallet pembayar, mis. DANA.

summary1

Tambahkan ringkasan: jumlah dan total seluruh hasil filter, per status, dan per sumber.

limitinteger

Jumlah per halaman, 1 sampai 200. Default 50.

before, before_idISO 8601, string

Halaman berikutnya: isi dengan next_before dan next_before_id dari respons sebelumnya.

curl "https://waroengpay.com/api/payments?status=paid&limit=50" \
-H "Authorization: Bearer $WAROENGPAY_API_KEY"
# halaman berikutnya: tambahkan &before=<next_before>&before_id=<next_before_id> dari respons sebelumnya
Respons
{
"data": [ { "id": "WRG-7K2M9QX4BD1PZ", "status": "paid", "amount": 50137, ... } ],
"has_more": true,
"next_before": "2026-09-24T03:00:00.000Z",
"next_before_id": "WRG-7K2M9QX4BD1PZ"
}

Batalkan tagihan

POST/api/payments/:id/cancel

Menutup tagihan yang masih menunggu. Pembayaran yang masuk setelah dibatalkan tidak akan dicocokkan.

curl -X POST https://waroengpay.com/api/payments/WRG-7K2M9QX4BD1PZ/cancel \
-H "Authorization: Bearer $WAROENGPAY_API_KEY"

Tagihan yang sudah lunas, kedaluwarsa, atau batal dijawab 409 not_pending. Kalau tagihan punya callback_url, event payment.cancelled ikut dikirim.

Siklus status

Setiap tagihan dimulai dari pending dan berakhir di salah satu dari tiga status final.

pending
dana masuk, nominal cocok paidlewat batas waktu + 5 menit expireddibatalkan lewat dashboard/API cancelled
Menunggupending

QR aktif dan bisa dibayar. Mutasi rekening dicek tiap 8 detik.

Lunaspaid

Kredit dengan nominal persis ditemukan di mutasi rekening dalam jendela waktu tagihan.

Kedaluwarsaexpired

Lewat batas waktu + 5 menit masa tenggang tanpa pembayaran yang cocok.

Batalcancelled

Dibatalkan lewat dashboard atau API sebelum dibayar.

Webhook

Kami mengirim POST JSON ke callback_url setiap kali status tagihan berubah jadi final.

Penjelasan lengkap cara kerja dan verifikasinya: panduan webhook pembayaran. Contoh integrasi dari awal: tutorial PHP & Laravel.

EventDikirim saat
payment.paidPembayaran cocok ditemukan, tagihan lunas. Bisa datang setelah payment.expired / payment.cancelled bila admin mencocokkan pembayaran telat (data.late = true).
payment.expiredTagihan lewat batas waktu tanpa pembayaran.
payment.cancelledTagihan dibatalkan.
pingTombol "Kirim tes" di bagian Kredensial.
Contoh kiriman
// Header
X-WaroengPay-Event: payment.paid
X-WaroengPay-Delivery: 1842
X-WaroengPay-Timestamp: 1790218992
X-Signature: 5f2b0c...e91a // hex HMAC-SHA256(secret, "<timestamp>.<body>")
// Body
{
"id": "evt_Qm2vK8sPz1Lx0aB7",
"event": "payment.paid",
"created_at": "2026-09-24T03:03:12.000Z",
"livemode": true, // false = event tagihan mode uji (sandbox)
"data": {
"id": "WRG-7K2M9QX4BD1PZ",
"reference": "INV-2041",
"status": "paid",
"base_amount": 50000,
"amount": 50137,
"pay_url": "https://waroengpay.com/pay/WRG-7K2M9QX4BD1PZ",
"paid_at": "2026-09-24T03:03:12.000Z",
"late": false, // true = pembayaran telat yang dicocokkan admin (bisa setelah payment.expired)
"livemode": true,
"link_id": null, // angka = dibuat pembeli lewat link pembayaran /l/<slug>
"payment": {
"issuer": "DANA",
"payer": "BU**I SAN**SO",
"bca_ref": "236433285",
"transaction_at": "2026-09-24T03:03:00.000Z"
}
}
}

Verifikasi tanda tangan

Hitung HMAC-SHA256(secret, timestamp + "." + body) dari body mentah, bandingkan dengan header X-Signature memakai perbandingan waktu-konstan, dan tolak timestamp yang lebih tua dari 5 menit supaya kiriman lama tidak bisa diputar ulang.

import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.WAROENGPAY_WEBHOOK_SECRET; // whsec_...
// pakai body mentah: tanda tangan dihitung dari teks persis yang dikirim
app.post("/webhook/waroengpay", express.raw({ type: "application/json" }), async (req, res) => {
const ts = req.get("x-waroengpay-timestamp") || "";
const sig = req.get("x-signature") || "";
const expected = crypto.createHmac("sha256", SECRET).update(`${ts}.${req.body}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300; // tolak kiriman ulang > 5 menit
if (!fresh || sig.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
if (event.livemode === false) return res.sendStatus(200); // event mode uji: jangan pernah menandai pesanan asli
if (event.event === "payment.paid") {
// cari pesanan lewat id tagihan yang disimpan saat membuat tagihan, BUKAN reference (tidak unik, bebas diisi)
const order = await db.orders.findByWaroengpayId(event.data.id);
if (!order || event.data.base_amount !== order.total) return res.sendStatus(200); // nominal beda: jangan tandai lunas
await db.orders.markPaid(order.id, event.id); // lewati kalau event.id sudah pernah diproses
}
res.sendStatus(200);
});
Gagal atau bukan 2xx? Dicoba ulang 6 kali: langsung, lalu +30 detik, +2 menit, +10 menit, +30 menit, +2 jam.
Balas 2xx secepatnya (batas 10 detik). Proses berat jalankan di belakang.
Event bisa terkirim lebih dari sekali. Simpan id event yang sudah diproses dan lewati duplikatnya.
Tanda tangan valid belum berarti pesanannya benar. Simpan id tagihan saat membuatnya, cari pesanan lewat data.id (bukan reference: siapa pun yang memegang API key-mu bisa memakai reference yang sama), pastikan data.base_amount sama dengan total pesanan, dan abaikan livemode: false.
URL ke jaringan privat (localhost, 10.x, 192.168.x, metadata cloud) ditolak. Redirect tidak diikuti.

Halaman bayar

Setiap tagihan punya halaman bayar siap pakai di pay_url.

Halaman bayar menampilkan QR, nominal persis, hitung mundur, tombol cek transaksi, dan berubah sendiri saat lunas. Setelah lunas, pembeli bisa mengunduh atau mencetak bukti bayar.
Kalau ada redirect_url, pembeli diarahkan ke sana 6 detik setelah lunas. Tagihan mode uji tidak dialihkan otomatis: alamatnya ditampilkan dan harus diklik.
Mau tampil di aplikasimu sendiri? Buat gambar QR dari string qris dengan library QR apa pun.
Jangan anggap pesanan lunas hanya karena pembeli kembali ke redirect_url. Selalu pegang webhook atau cek status lewat API.
Kepala halaman bayar menampilkan logo, nama toko, dan centang verifikasi dari Akun → Profil toko. Mengganti nama, tagline, atau logo toko mencabut centang sampai admin mengecek ulang.
Tagihan dari link pembayaran (/l/slug) punya link_id, reference kosong, dan webhook-nya dikirim ke URL webhook default akunmu. Bila link meminta data pembeli, isiannya ada di customer (name, phone WhatsApp 62…, note).

Error & batasan

Semua error berbentuk { "error": "pesan untuk manusia", "code": "kode_mesin" }.

HTTPcodeArtinya
400invalid_amount, invalid_url, invalid_expires_in, invalid_json, invalid_actionInput tidak valid. Baca pesan error-nya.
401unauthorized, invalid_api_keyAPI key tidak ada, salah, atau sudah dibuat ulang.
403account_not_active, forbidden, live_invoiceAkun belum disetujui/nonaktif, tidak punya akses, atau simulasi pada tagihan live.
404not_foundTagihan tidak ada atau bukan milikmu.
409not_pending, not_finalAksi tidak cocok dengan status tagihan saat ini.
413payload_too_largeBody lebih dari 20 KB.
429rate_limited, too_many_pendingTerlalu banyak request. Lihat header Retry-After.
503amount_busyKode unik untuk nominal itu habis sementara. Coba lagi sebentar.
Nominal
Rp1.000 sampai Rp10.000.000, ditambah kode unik kecil (default Rp100 sampai Rp250; baca unique_code di respons).
Masa berlaku
5 sampai 60 menit (default 15), plus 5 menit masa tenggang.
Tagihan menunggu
Maksimal 50 per akun dalam satu waktu.
Pembuatan
Maksimal 30 tagihan per menit per akun.
Request
Maksimal 300 request per menit per alamat IP.
Body
JSON maksimal 20 KB per request.

Coba langsung

Kirim request POST /api/payments sungguhan memakai sesi dashboard, lalu lihat respons mentahnya.

Setelah masuk, bagian ini mengirim request sungguhan dari dashboard dan menampilkan respons mentahnya.

Sandbox (mode uji)

POST/api/payments/:id/simulate

Uji integrasi dari buat tagihan sampai webhook tanpa uang sungguhan. Data uji terpisah total dari data live.

Pakai API key wp_test_ dari bagian Kredensial. Endpoint-nya sama persis; tagihan uji punya livemode: false.
QR tagihan uji sengaja tidak bisa dibayar. Lunasi atau kedaluwarsakan lewat endpoint ini, atau tombol di halaman bayarnya.
Webhook uji hanya dikirim ke callback_url yang diisi saat membuat tagihan uji (bukan URL default), ditandatangani secret mode uji. Server produksi yang memakai secret live otomatis menolaknya; tetap abaikan livemode: false.
Key live tidak bisa membaca tagihan uji, dan sebaliknya. Tagihan uji tidak masuk saldo, statistik, atau notifikasi.
actionstring

pay = lunas (issuer SANDBOX, bca_ref palsu), expire = kedaluwarsa. Default pay. Hanya untuk tagihan uji yang masih pending.

curl -X POST https://waroengpay.com/api/payments/WRG-7K2M9QX4BD1PZ/simulate \
-H "Authorization: Bearer $WAROENGPAY_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"pay"}' # atau "expire"

Tagihan live dijawab 403 live_invoice, tagihan uji yang sudah final 409 not_pending. Tagihan uji yang tidak disimulasikan kedaluwarsa sendiri seperti tagihan live.