Dokumentasi API PayBridge
Terima pembayaran QRIS dan Virtual Account dengan satu API sederhana. Tanpa signature rumit, tanpa menunggu — daftar, ambil API key, dan transaksi uji pertamamu jalan dalam hitungan menit.
Mulai dalam 10 menit
1. Daftar akun → menu Developer → buat aplikasi → salin API key
(pb_test_…).
2. Buat pembayaran pertama:
curl -X POST https://pay.idcloudhost.com/v1/payments \
-H "Authorization: Bearer pb_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv-001" \
-d '{"amount": 150000, "rail": "qris", "reference": "INV/001"}'
Respons berisi qr_content — render sebagai gambar QR (mis. npm
qrcode) dan tunjukkan ke pembayar.
3. Set webhook URL di menu Developer, lalu buka pembayaran tadi di
portal dan klik Simulasikan lunas. Webhook
payment.paid tiba di server-mu — integrasi selesai.
Autentikasi
Semua request memakai API key di header Authorization:
Authorization: Bearer pb_test_xxx
API key diterbitkan per aplikasi di portal, tampil sekali saat dibuat. Tidak ada signature, timestamp, atau header tambahan — cukup key ini lewat HTTPS.
Test vs live
Environment ditentukan oleh prefix key, bukan base URL — kode integrasi tidak berubah:
| Key | Perilaku |
|---|---|
pb_test_… | Sandbox — transaksi tidak nyata, bisa dilunasi lewat tombol simulasi di portal. |
pb_live_… | Produksi — dana riil, tersedia setelah verifikasi usaha disetujui. |
Idempotency
Semua POST wajib menyertakan header Idempotency-Key — pakai ID unik
dari sistemmu (mis. nomor invoice). Request ulang dengan key sama mengembalikan hasil pertama
(HTTP 200), tidak pernah membuat transaksi ganda. Timeout? Kirim ulang saja dengan key
yang sama.
Rate limit
120 request per menit per API key. Terlampaui → HTTP 429 dengan header
Retry-After (detik). Hormati nilainya, lalu ulangi request yang sama
(aman — idempotency).
Membuat pembayaran
POST /v1/payments
| Field | Tipe | Keterangan |
|---|---|---|
amount | integer, wajib | Rupiah utuh: 150000 = Rp150.000. Tanpa desimal. |
rail | string, wajib | "qris" atau "va". |
reference | string, wajib | ID tagihan di sistemmu (≤128 karakter). Dikembalikan di webhook. |
va_bank | string | Wajib bila rail=va. Contoh: bmri, brin, bnia. |
expires_in | integer | Masa berlaku (detik). Default: QRIS 3600, VA 86400. |
customer | object | Opsional: {"name","phone"} — nama tampil di VA. |
metadata | object | Bebas; dikembalikan apa adanya di respons & webhook. |
Respons 201:
{
"id": "pay_x8Kj2…",
"status": "pending",
"amount": 150000,
"rail": "qris",
"reference": "INV/001",
"qr_content": "00020101…", // rail=qris → render jadi QR
"va_number": null, // rail=va → nomor VA
"va_bank": null,
"hosted_url": "https://pay.idcloudhost.com/p/pay_x8Kj2…",
"expires_at": "2026-08-19T15:30:00.000Z",
"paid_at": null,
"metadata": null,
"created_at": "2026-08-19T14:30:00.000Z"
}
Halaman bayar hosted — tanpa frontend sama sekali
Setiap payment membawa hosted_url: halaman bayar siap pakai yang bisa kamu kirim
langsung ke pelanggan (WhatsApp, email, SMS). Halaman itu menampilkan nama usahamu, nominal,
QR yang sudah dirender (atau nomor VA + tombol salin), memperbarui statusnya sendiri saat
pembayaran masuk, dan menangani kedaluwarsa. Kalau kamu tidak ingin membangun tampilan
pembayaran sendiri, cukup teruskan tautan ini — integrasi minimummu tinggal dua hal:
buat payment, dengarkan webhook.
Siklus status
pending ──► paid ──► refunded
├──────► expired
└──────► failed
Hanya lima status, tidak ada yang keenam. Kasus tak lazim dari penyedia (mis. lebih bayar)
masuk array flags — integrasi sederhana boleh mengabaikannya dan tetap benar.
Membaca pembayaran
GET /v1/payments/{id} — satu pembayaran.
GET /v1/payments?reference=INV/001 — cari berdasarkan referensimu.
GET /v1/payments?status=paid&limit=100 — daftar, terbaru dulu.
Objek yang dikembalikan selalu berbentuk sama dengan respons create. Gunakan ini sebagai jaring pengaman (mis. cron rekonsiliasi) — mekanisme utama pelunasan adalah webhook.
Refund
POST /v1/payments/{id}/refunds
curl -X POST https://pay.idcloudhost.com/v1/payments/pay_x8Kj2/refunds \
-H "Authorization: Bearer pb_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: rf-001" \
-d '{"amount": 50000, "reason": "Kelebihan bayar"}'
amount opsional (default: penuh). Refund parsial boleh berulang sampai total
= nominal asal. Hanya pembayaran berstatus paid yang bisa direfund.
Menerima webhook
Set webhook URL per aplikasi di portal. Event dikirim sebagai
POST JSON:
| Event | Kapan |
|---|---|
payment.paid | Pembayaran masuk. Payload lengkap — cukup untuk menandai invoice lunas & membuka layanan tanpa API call tambahan. |
payment.expired | Lewat masa berlaku. |
refund.succeeded / refund.failed | Hasil refund. |
{
"id": "evt_9aB3…", // dedup dengan id ini
"type": "payment.paid",
"created_at": "2026-08-19T14:35:12.000Z",
"data": { …objek payment utuh… }
}
Balas 2xx secepatnya (proses berat kerjakan async). Aturan yang menyelamatkan
kamu dari insiden klasik:
- Dedup per
idevent — pengiriman minimal-sekali, duplikat mungkin terjadi. - Jangan andalkan urutan tiba — nilai kebenaran ada di field
status, bukan urutan.
Verifikasi signature
Setiap webhook membawa header PayBridge-Signature:
PayBridge-Signature: t=1755612912,v1=5f8a…
v1 = HMAC-SHA256(signing secret, t + "." + body).
Signing secret ada di menu Developer. Node.js:
const crypto = require('node:crypto');
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map(s => s.split('=')));
const calc = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(calc));
}
PHP:
function verify(string $rawBody, string $header, string $secret): bool {
parse_str(str_replace(',', '&', $header), $p);
$calc = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
return hash_equals($calc, $p['v1']);
}
Retry & replay
Webhook yang tidak dibalas 2xx diulang dengan backoff:
1m → 5m → 15m → 1j → 3j → 6j → 12j → 24j. Setelah itu masuk dead-letter dan bisa
di-replay kapan pun dari halaman Webhook di portal — bersama seluruh riwayat
pengiriman, status, dan jumlah percobaannya.
Kode error
Semua error berbentuk sama — code stabil untuk mesin, message
menyebut nilai yang salah, doc_url menaut kemari:
{ "error": { "code": "…", "message": "…", "doc_url": "…" } }
| HTTP | Code | Arti & solusi |
|---|---|---|
| 401 | missing_api_key | Header Authorization: Bearer … tidak ada. Tambahkan API key-mu. |
| 401 | invalid_api_key | Key tidak dikenal — cek salin-tempelnya utuh dan aplikasinya masih ada. |
| 403 | app_disabled | Aplikasi pemilik key ini dinonaktifkan. Aktifkan kembali di portal → Developer. |
| 403 | rail_not_live | Metode ini belum disetujui untuk live di akunmu (approval berjalan per rel — VA bisa aktif duluan sementara QRIS menunggu). message menyebut rel yang sudah aktif; status lengkap di portal. Sandbox tidak terpengaruh. |
| 400 | missing_idempotency_key | Semua POST wajib header Idempotency-Key. Pakai ID unik dari sistemmu. |
| 400 | invalid_request | Ada field yang salah — message menyebut field mana dan formatnya yang benar. |
| 404 | payment_not_found | ID tidak ada di akunmu. Key test tidak bisa membaca payment live (dan sebaliknya — cek tenant & env). |
| 409 | payment_not_refundable | Refund hanya untuk status paid. |
| 400 | invalid_refund_amount | Nominal melebihi sisa yang bisa direfund. |
| 502 | provider_refund_failed | Penyedia menolak refund — message membawa alasan aslinya. |
| 429 | rate_limited | Lewati batas 120 req/menit. Tunggu sesuai Retry-After, lalu ulangi request yang sama. |
| 500 | internal_error | Kesalahan di sisi kami — sudah tercatat di log kami. Ulangi dengan Idempotency-Key yang sama; aman. |
Sandbox
Akun baru langsung berada di mode sandbox — tidak perlu menunggu apa pun untuk mulai menulis kode. Perilaku sandbox identik dengan produksi (endpoint, format, error), bedanya:
- Pembayaran tidak melibatkan dana riil.
- Pelunasan dipicu manual: buka pembayaran di portal → Simulasikan lunas —
webhook
payment.paidterkirim sungguhan ke server-mu. - Jalan ke produksi dibuka lewat Sertifikasi — mandiri, tanpa menunggu review manual.
Sertifikasi → live
Akses live tidak menunggu email atau rapat sign-off. Halaman Sertifikasi di portal berisi 9 butir yang tercentang otomatis dari bukti nyata saat integrasimu melakukannya di sandbox:
| Butir | Cara lulus |
|---|---|
| Membuat pembayaran | POST /v1/payments pertama-mu. |
| Idempotency | Kirim ulang request dengan Idempotency-Key yang sama. |
| Membaca status | GET /v1/payments/… (pola jaring pengaman). |
| Webhook terpasang | Set webhook URL di menu Developer. |
Menerima payment.paid | Simulasikan lunas; endpoint-mu membalas 2xx. |
Menerima payment.expired | Buat payment expires_in: 60, biarkan kedaluwarsa. |
| Refund | Refund payment test yang sudah paid. |
| Menolak signature palsu | Tombol Uji webhook mengirim event ber-signature salah — endpoint-mu harus menolaknya. |
| Tahan webhook ganda | Uji webhook mengirim event sama dua kali — keduanya harus dibalas 2xx. |
Sembilan centang penuh → akses live terbuka detik itu juga: buat key
pb_live_, ganti prefix key di konfigurasi, selesai.
rail_not_live
di live, dan tetap bisa dipakai penuh di sandbox.Butir-butir ini bukan formalitas — mereka persis daftar penyebab insiden payment paling umum, jadi lulus sertifikasi berarti integrasimu sudah tahan di skenario yang biasanya baru ketahuan di produksi.
PayBridge · pay.idcloudhost.com · Pertanyaan? alfian@idcloudhost.com