Dokumentasi Reseller
API Reseller PublikAPI
Panduan lengkap integrasi server-to-server untuk reseller. Mencakup autentikasi, katalog produk, buat & cek order, manajemen saldo, konfigurasi webhook, dan verifikasi HMAC signature.
POST /api/reseller/orders
mengembalikan status antrean awal. Hasil final (completed / failed)
dikirimkan ke URL webhook Anda via order.completed /
order.failed. Cocokkan dengan orderId
internal Anda.
Quick start
- Login ke dashboard lalu buat API Key di menu API Keys.
- Cek saldo awal lewat
GET /api/reseller/balance. - Ambil daftar produk lewat
GET /api/reseller/productsdan simpan mappingid-nya. - Buat order lewat
POST /api/reseller/ordersdenganexternal_idunik dari sistem Anda. - Konfigurasikan
webhook_urllewatPATCH /api/reseller/integrationuntuk menerima hasil final order secara real-time. - Verifikasi
X-Webhook-Signaturedi setiap request masuk menggunakanwebhook_secretyang tersimpan.
Autentikasi
Semua endpoint memerlukan header X-API-Key dengan nilai API key aktif milik Anda.
| Header | Wajib | Keterangan |
|---|---|---|
X-API-Key |
Ya | API key reseller. Simpan di backend — jangan expose ke browser atau aplikasi client. |
Content-Type |
Untuk POST/PATCH | application/json |
Accept |
Disarankan | application/json |
?api_key=pk_xxx untuk keperluan testing saja.
Untuk produksi selalu gunakan header agar secret tidak bocor ke log
URL atau browser history.
Ringkasan endpoint
| Method | Path | Fungsi |
|---|---|---|
| GET | /api/reseller/products |
Daftar produk aktif beserta harga dan status stok. |
| POST | /api/reseller/orders |
Buat order baru. Idempoten berdasarkan external_id. |
| GET | /api/reseller/orders/:external_id |
Cek status order berdasarkan external_id Anda. |
| GET | /api/reseller/balance |
Cek saldo Anda dalam IDR. |
| GET | /api/reseller/integration |
Lihat konfigurasi integrasi: webhook URL, secret, status aktif. |
| PATCH | /api/reseller/integration |
Perbarui webhook_url untuk menerima callback order. |
GET /api/reseller/products
Kembalikan semua produk aktif yang dapat dipesan. Gunakan nilai id sebagai product_id saat membuat order.
curl https://publikapi.com/api/reseller/products \
-H "X-API-Key: pk_xxx"
{
"success": true,
"data": [
{
"id": "voucher_1m",
"name": "Voucher koin emas 1M",
"price": 200,
"stock": "in_stock"
},
{
"id": "giftcard_100m",
"name": "Koleksi Koin 100M",
"price": 1500,
"stock": "in_stock"
}
]
}
stock bisa bernilai "in_stock" atau
"out_of_stock". Produk dengan status
"out_of_stock" akan ditolak saat order.
POST /api/reseller/orders
Buat order baru. Saldo Anda akan langsung dikurangi sebesar harga produk × quantity. Order masuk ke antrean dan diproses otomatis.
Body request
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
external_id |
string | Ya | ID order unik dari sistem Anda (maks 128 karakter). Berfungsi sebagai idempotency key — order duplikat dengan external_id yang sama tidak akan dibuat ulang. |
product_id |
string | Ya | ID produk dari GET /api/reseller/products. |
target |
string | Ya | User ID game / target pengiriman produk. |
quantity |
integer | Tidak | Jumlah item. Default 1. |
curl -X POST https://publikapi.com/api/reseller/orders \
-H "X-API-Key: pk_xxx" \
-H "Content-Type: application/json" \
-d '{
"external_id": "INV-20260418-0001",
"product_id": "voucher_1m",
"target": "1234567",
"quantity": 1
}'
{
"success": true,
"data": {
"external_id": "INV-20260418-0001",
"orderId": "d152a1bc-315d-49ef-aecd-600732a432e3",
"product_id": "voucher_1m",
"target": "1234567",
"quantity": 1,
"totalCost": 200,
"status": "queued",
"createdAt": "2026-04-22T02:46:53.656Z",
"position": 0
}
}
external_id yang sama
dikirim ulang, server mengembalikan data order sebelumnya dengan field
"replayed": true tanpa membuat order atau debit baru.
Gunakan fitur ini untuk retry aman saat timeout jaringan.
Contoh Node.js
const res = await fetch("https://publikapi.com/api/reseller/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.PUBLIKAPI_KEY
},
body: JSON.stringify({
external_id: "INV-20260418-0001",
product_id: "voucher_1m",
target: "1234567",
quantity: 1
})
});
const data = await res.json();
console.log(data.data.orderId, data.data.status);
Contoh PHP
<?php
$ch = curl_init("https://publikapi.com/api/reseller/orders");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: " . getenv("PUBLIKAPI_KEY"),
],
CURLOPT_POSTFIELDS => json_encode([
"external_id" => "INV-20260418-0001",
"product_id" => "voucher_1m",
"target" => "1234567",
"quantity" => 1,
]),
]);
$body = curl_exec($ch);
curl_close($ch);
$data = json_decode($body, true);
echo $data["data"]["orderId"];
Contoh Python
import os, requests
resp = requests.post(
"https://publikapi.com/api/reseller/orders",
headers={
"Content-Type": "application/json",
"X-API-Key": os.environ["PUBLIKAPI_KEY"],
},
json={
"external_id": "INV-20260418-0001",
"product_id": "voucher_1m",
"target": "1234567",
"quantity": 1,
},
timeout=15,
)
resp.raise_for_status()
data = resp.json()
print(data["data"]["orderId"], data["data"]["status"])
GET /api/reseller/orders/:external_id
Cek status order menggunakan external_id yang Anda buat. Tersedia untuk polling fallback jika webhook tidak diterima.
curl https://publikapi.com/api/reseller/orders/INV-20260418-0001 \
-H "X-API-Key: pk_xxx"
{
"success": true,
"data": {
"external_id": "INV-20260418-0001",
"orderId": "d152a1bc-315d-49ef-aecd-600732a432e3",
"product_id": "voucher_1m",
"target": "1234567",
"quantity": 1,
"totalCost": 200,
"status": "completed",
"error": null,
"createdAt": "2026-04-22T02:46:53.656Z",
"completedAt": "2026-04-22T02:46:54.157Z"
}
}
Nilai status yang mungkin
| Status | Arti |
|---|---|
queued | Menunggu diproses dalam antrean. |
processing | Sedang diproses oleh vendor. |
completed | Berhasil. Produk telah dikirimkan ke target. |
failed | Gagal. Lihat field error untuk keterangan. Saldo akan di-refund otomatis. |
GET /api/reseller/balance
Cek saldo Anda saat ini dalam IDR.
curl https://publikapi.com/api/reseller/balance \
-H "X-API-Key: pk_xxx"
{
"success": true,
"data": {
"balance": 400,
"currency": "IDR"
}
}
error pada status order.
Konfigurasi integrasi & webhook
GET /api/reseller/integration
Lihat konfigurasi integrasi aktif Anda termasuk webhook URL dan secret.
curl https://publikapi.com/api/reseller/integration \
-H "X-API-Key: pk_xxx"
{
"success": true,
"data": {
"integration_type": "bukaolshop",
"webhook_url": "https://myapp.com/api/webhooks/publikapi",
"webhook_secret": "d0a743a809a977cc0a0cfce3b35495e44aece618805d0f5ccd82187850c8bd38",
"active": true,
"createdAt": "2026-04-22T02:17:10.793Z",
"updatedAt": "2026-04-22T02:46:00.000Z"
}
}
webhook_secret dengan aman.
Nilai ini digunakan untuk memverifikasi HMAC signature setiap request
webhook yang masuk ke server Anda.
PATCH /api/reseller/integration
Set atau ubah URL endpoint webhook Anda. URL harus bisa menerima HTTP POST dari server PublikAPI.
curl -X PATCH https://publikapi.com/api/reseller/integration \
-H "X-API-Key: pk_xxx" \
-H "Content-Type: application/json" \
-d '{"webhook_url": "https://myapp.com/api/webhooks/publikapi"}'
{
"success": true,
"data": {
"integration_type": "bukaolshop",
"webhook_url": "https://myapp.com/api/webhooks/publikapi",
"webhook_secret": "d0a743a809a977cc0a0cfce3b35495e44aece618805d0f5ccd82187850c8bd38",
"active": true,
"createdAt": "2026-04-22T02:17:10.793Z",
"updatedAt": "2026-04-22T02:50:00.000Z"
}
}
Webhook callback
Setelah webhook_url dikonfigurasi, PublikAPI akan mengirimkan
HTTP POST ke URL tersebut saat status order berubah.
Events yang dikirim
order.queued— order masuk ke antreanorder.completed— order berhasil diselesaikanorder.failed— order gagal, saldo dikembalikan
Header request webhook
Content-Type: application/jsonX-Webhook-Signature: sha256=<hex>X-Webhook-Id: <webhook_id>X-Webhook-Event: order.completedUser-Agent: PublikAPI-Webhook/1.0
Contoh payload
{
"event": "order.completed",
"data": {
"orderId": "d152a1bc-315d-49ef-aecd-600732a432e3",
"gameId": "1234567",
"product": "voucher_1m",
"quantity": 1,
"status": "completed",
"completedAt": "2026-04-22T02:46:54.157Z"
},
"timestamp": "2026-04-22T02:46:54.158Z"
}
orderId dari payload dengan orderId
yang Anda simpan saat membuat order untuk mengetahui mana
external_id yang selesai.
Retry otomatis
- Timeout pengiriman: 10 detik per percobaan.
- Retry otomatis hingga 3 kali jika respons bukan
2xx. - Jeda retry: 5 detik → 30 detik → 120 detik.
- Endpoint Anda sebaiknya merespons
200 OKsesegera mungkin, lalu proses payload secara async.
Verifikasi HMAC signature — Node.js
import crypto from "node:crypto";
// Express contoh — ambil raw body sebelum JSON parse
app.use("/api/webhooks/publikapi", express.raw({ type: "application/json" }));
app.post("/api/webhooks/publikapi", (req, res) => {
const rawBody = req.body; // Buffer
const sig = req.headers["x-webhook-signature"] || "";
const secret = process.env.PUBLIKAPI_WEBHOOK_SECRET;
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const valid = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!valid) return res.status(401).send("Invalid signature");
const payload = JSON.parse(rawBody);
console.log(payload.event, payload.data.orderId);
res.sendStatus(200);
});
Verifikasi HMAC signature — PHP
<?php
$rawBody = file_get_contents("php://input");
$sig = $_SERVER["HTTP_X_WEBHOOK_SIGNATURE"] ?? "";
$secret = getenv("PUBLIKAPI_WEBHOOK_SECRET");
$expected = "sha256=" . hash_hmac("sha256", $rawBody, $secret);
if (!hash_equals($expected, $sig)) {
http_response_code(401);
exit("Invalid signature");
}
$payload = json_decode($rawBody, true);
error_log($payload["event"] . " " . $payload["data"]["orderId"]);
http_response_code(200);
Verifikasi HMAC signature — Python
import hashlib, hmac, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["PUBLIKAPI_WEBHOOK_SECRET"].encode()
@app.post("/api/webhooks/publikapi")
def webhook():
raw = request.get_data()
sig = request.headers.get("X-Webhook-Signature", "")
mac = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(mac, sig):
abort(401)
payload = request.json
print(payload["event"], payload["data"]["orderId"])
return "", 200
Error handling
| HTTP | Arti | Tindakan |
|---|---|---|
400 |
Field wajib kosong, produk tidak ditemukan, saldo tidak cukup, atau external_id tidak valid. |
Perbaiki request. Jangan retry buta. |
401 |
API key hilang, tidak valid, atau integrasi tidak aktif. | Cek header X-API-Key dan status integrasi. |
404 |
Order dengan external_id tersebut tidak ditemukan. |
Pastikan external_id benar dan milik akun Anda. |
429 |
Melebihi rate limit order (20 req/menit). | Kurangi frekuensi atau antre di sisi Anda. Hormati header Retry-After. |
500 |
Error internal server. | Retry dengan external_id yang sama — idempotency aman. |
{ "success": false, "error": "pesan error" }
Header rate limit
X-RateLimit-Limit— limit pada window aktif.X-RateLimit-Remaining— sisa request pada window aktif.X-RateLimit-Reset— epoch detik saat window reset.Retry-After— hanya muncul saat kena429.
Checklist keamanan
- Simpan
API keydanwebhook_secrethanya di backend — jangan hardcode di kode atau repo. - Gunakan HTTPS. Jangan kirim API key lewat plain HTTP.
- Selalu verifikasi
X-Webhook-Signaturemenggunakan raw body sebelum memproses payload. - Gunakan
external_idyang unik dan tidak bisa ditebak per order. - Pada error
500atau timeout, retry denganexternal_idyang sama agar tidak terjadi double order. - Catat semua request, response, dan event webhook beserta
external_iddanorderIdlokal Anda. - Jika API key dicurigai bocor, segera hapus dan buat key baru dari dashboard.