Dokumentasi
Integrasi API H2H
Jual produk Zapup dari sistemmu sendiri: cek saldo, ambil daftar produk dan harga, buat transaksi, lalu terima status akhirnya lewat callback.
Ringkasan
Semua endpoint memakai JSON dan diawali base URL berikut:
https://api.zapup.id/api/open-api/v1/h2h
Alurnya: cek saldo → ambil produk → buat transaksi dengan ref_id milikmu → terima callback saat transaksi selesai (atau cek status dengan ref_id yang sama). Transaksi dibayar dari saldo akunmu; kalau transaksi gagal, saldo dikembalikan otomatis.
Mendapatkan API key
API key dibuat oleh admin Zapup untuk akun resellermu. Saat dibuat kamu menerima dua rahasia yang hanya ditampilkan sekali:
- API key, berbentuk
zk_xxxxxxxx.rahasia, untuk memanggil API. - Callback secret, untuk memverifikasi callback yang dikirim Zapup.
Berikan juga callback URL (http/https publik) tujuan status akhir, dan bila perlu daftar IP whitelist server-mu. Simpan kedua rahasia di server, jangan di aplikasi klien.
Autentikasi
Kirim API key di header X-API-Key pada setiap permintaan.
curl https://api.zapup.id/api/open-api/v1/h2h/balance \ -H "X-API-Key: zk_xxxxxxxx.rahasia"
| Field | Tipe | Keterangan |
|---|---|---|
| 401 | HTTP | API key salah, tidak aktif, atau akunnya nonaktif. |
| 403 | HTTP | Permintaan datang dari IP di luar whitelist API key. |
Format respons & error
Setiap respons dibungkus { "data": ..., "error": ... }. Periksa status HTTP terlebih dulu.
{
"data": { "balance": 1250000 },
"error": null
}Kode 2001 berarti kesalahan validasi atau aturan bisnis; pesannya bisa langsung ditampilkan. Error 5xx berisi pesan dengan kode referensi, yang bisa kamu kirim ke tim Zapup untuk ditelusuri.
Batas permintaan
| Field | Tipe | Keterangan |
|---|---|---|
| per IP | 600 / menit | Dihitung sebelum autentikasi. |
| per akun | 1200 / menit | Semua API key milik akun yang sama. |
Melewati batas dijawab 429 dengan pesan “terlalu banyak request”. Tunggu sebentar lalu coba lagi.
Cek saldo
{
"data": { "balance": 1250000 },
"error": null
}Daftar produk
Mengembalikan semua produk aktif beserta harga untuk akunmu (sudah termasuk tingkatan harga dan markup upline).
| Field | Tipe | Keterangan |
|---|---|---|
| code | string | Kode produk, dipakai sebagai product_code saat transaksi. |
| name | string | Nama produk. |
| category / provider | string | Kategori dan provider produk. |
| price | number | Harga untukmu. Untuk produk bebas nominal ini adalah biaya admin; total = price + amount. |
| is_open_amount | boolean | true bila produk bebas nominal (kirim amount saat transaksi). |
| min_amount / max_amount | number | Batas nominal produk bebas nominal (0 = tanpa batas). |
| is_available | boolean | false saat produk gangguan atau di luar jam operasional. |
| unavailable_reason | string | Alasan saat is_available false. |
Buat transaksi
| Field | Tipe | Keterangan |
|---|---|---|
| product_code | string | Wajib. Kode dari daftar produk. |
| customer_no | string | Wajib. Nomor tujuan: nomor HP, ID pelanggan PLN, dsb. |
| ref_id | string | Wajib, maks. 64 karakter, unik per akun. Kunci idempotensi. |
| amount | number | Wajib untuk produk bebas nominal, dalam batas min/max produk. |
| max_price | number | Opsional. Transaksi ditolak bila total harga di atasnya (0 = tanpa batas). |
curl -X POST https://api.zapup.id/api/open-api/v1/h2h/transaction \
-H "X-API-Key: zk_xxxxxxxx.rahasia" \
-H "Content-Type: application/json" \
-d '{
"product_code": "PLN20",
"customer_no": "51234567890",
"ref_id": "INV-20931",
"max_price": 21000
}'{
"data": {
"ref_id": "INV-20931",
"order_no": "ORD-00012345",
"product_code": "PLN20",
"customer_no": "51234567890",
"status": "pending",
"price": 20850
},
"error": null
}Biasanya status awal pending: pesanan sedang dikirim ke supplier. Status akhir datang lewat callback atau endpoint cek status. Penolakan umum: produk tidak ditemukan (404), saldo tidak cukup, harga di atas max_price, produk sedang gangguan, atau nominal di luar batas.
Cek status
Bentuk respons sama dengan buat transaksi.
| Field | Tipe | Keterangan |
|---|---|---|
| pending | status | Masih diproses (termasuk saat dialihkan ke supplier lain). |
| success | status | Berhasil. sn berisi serial number / token dari supplier. |
| failed | status | Gagal di semua supplier. Saldo sudah dikembalikan. |
Callback
Saat transaksi mencapai status akhir (success atau failed), Zapup mengirim POST ke callback URL-mu dengan body yang sama seperti respons cek status (tanpa pembungkus data).
| Field | Tipe | Keterangan |
|---|---|---|
| Content-Type | header | application/json |
| X-Zapup-Event | header | transaction.final |
| X-Zapup-Signature | header | sha256=<hex HMAC-SHA256 dari body mentah, dengan callback secret> |
{
"ref_id": "INV-20931",
"order_no": "ORD-00012345",
"product_code": "PLN20",
"customer_no": "51234567890",
"status": "success",
"sn": "1234-5678-9012-3456-7890",
"price": 20850
}Selalu verifikasi tanda tangan dengan body mentah (sebelum di-parse) dan bandingkan secara constant-time:
import crypto from "node:crypto";
// Express: pakai body mentah, bukan JSON yang sudah di-parse
app.post("/zapup/callback", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.ZAPUP_CALLBACK_SECRET)
.update(req.body) // Buffer body mentah
.digest("hex");
const got = req.get("X-Zapup-Signature") || "";
const ok = got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok) return res.status(401).end();
const trx = JSON.parse(req.body.toString("utf8"));
// update pesanan dengan trx.ref_id -> trx.status (success | failed), trx.sn
res.status(200).end();
});ref_id). Callback hanya dikirim ke alamat publik.Idempotensi & retry
- Mengirim ulang
POST /transactiondenganref_idyang sama tidak membuat transaksi baru; kamu menerima transaksi yang sudah ada. - Kalau koneksi putus sebelum respons datang, kirim ulang permintaan yang sama atau panggil cek status. Jangan membuat
ref_idbaru untuk pesanan yang sama. - Gunakan callback sebagai sumber utama status, dan cek status sebagai cadangan (misalnya tiap beberapa menit untuk pesanan yang masih pending).
Checklist go-live
- API key dan callback secret disimpan di server, tidak pernah di aplikasi klien.
- IP server produksi sudah didaftarkan di whitelist (bila dipakai).
- Setiap pesanan punya ref_id unik yang juga disimpan di sistemmu.
- Callback memverifikasi X-Zapup-Signature dan membalas 2xx dengan cepat.
- Pesanan pending lama dicek ulang lewat GET /transaction/{ref_id}.
- Pakai max_price untuk mencegah harga berubah di luar dugaan.