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:

Base URL
https://api.zapup.id/api/open-api/v1/h2h

Alurnya: cek saldo ambil produkbuat 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
curl https://api.zapup.id/api/open-api/v1/h2h/balance \
  -H "X-API-Key: zk_xxxxxxxx.rahasia"
FieldTipeKeterangan
401HTTPAPI key salah, tidak aktif, atau akunnya nonaktif.
403HTTPPermintaan 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

FieldTipeKeterangan
per IP600 / menitDihitung sebelum autentikasi.
per akun1200 / menitSemua API key milik akun yang sama.

Melewati batas dijawab 429 dengan pesan “terlalu banyak request”. Tunggu sebentar lalu coba lagi.

Cek saldo

GEThttps://api.zapup.id/api/open-api/v1/h2h/balance
Respons
{
  "data": { "balance": 1250000 },
  "error": null
}

Daftar produk

GEThttps://api.zapup.id/api/open-api/v1/h2h/products

Mengembalikan semua produk aktif beserta harga untuk akunmu (sudah termasuk tingkatan harga dan markup upline).

FieldTipeKeterangan
codestringKode produk, dipakai sebagai product_code saat transaksi.
namestringNama produk.
category / providerstringKategori dan provider produk.
pricenumberHarga untukmu. Untuk produk bebas nominal ini adalah biaya admin; total = price + amount.
is_open_amountbooleantrue bila produk bebas nominal (kirim amount saat transaksi).
min_amount / max_amountnumberBatas nominal produk bebas nominal (0 = tanpa batas).
is_availablebooleanfalse saat produk gangguan atau di luar jam operasional.
unavailable_reasonstringAlasan saat is_available false.

Buat transaksi

POSThttps://api.zapup.id/api/open-api/v1/h2h/transaction
FieldTipeKeterangan
product_codestringWajib. Kode dari daftar produk.
customer_nostringWajib. Nomor tujuan: nomor HP, ID pelanggan PLN, dsb.
ref_idstringWajib, maks. 64 karakter, unik per akun. Kunci idempotensi.
amountnumberWajib untuk produk bebas nominal, dalam batas min/max produk.
max_pricenumberOpsional. Transaksi ditolak bila total harga di atasnya (0 = tanpa batas).
cURL
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
  }'
Respons
{
  "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

GEThttps://api.zapup.id/api/open-api/v1/h2h/transaction/{ref_id}

Bentuk respons sama dengan buat transaksi.

FieldTipeKeterangan
pendingstatusMasih diproses (termasuk saat dialihkan ke supplier lain).
successstatusBerhasil. sn berisi serial number / token dari supplier.
failedstatusGagal di semua supplier. Saldo sudah dikembalikan.
price adalah total yang dipotong dari saldo; untuk produk bebas nominal ada juga amount (nominal yang dikirim ke pelanggan).

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).

FieldTipeKeterangan
Content-Typeheaderapplication/json
X-Zapup-Eventheadertransaction.final
X-Zapup-Signatureheadersha256=<hex HMAC-SHA256 dari body mentah, dengan callback secret>
Body
{
  "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();
});
Balas dengan status 2xx dalam 10 detik. Respons lain atau timeout dicoba ulang oleh Zapup hingga 8 kali dengan jeda yang makin panjang, jadi proses callback-mu harus aman bila diterima lebih dari sekali (cocokkan dengan ref_id). Callback hanya dikirim ke alamat publik.

Idempotensi & retry

  • Mengirim ulang POST /transaction dengan ref_id yang 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_id baru 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.
Zapup · Dokumentasi API H2H