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.

Base URL https://publikapi.com
Auth X-API-Key: pk_xxx
Idempotency external_id per order
Rate limit order 360 request per menit per user
Catatan: 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

  1. Login ke dashboard lalu buat API Key di menu API Keys.
  2. Cek saldo awal lewat GET /api/reseller/balance.
  3. Ambil daftar produk lewat GET /api/reseller/products dan simpan mapping id-nya.
  4. Buat order lewat POST /api/reseller/orders dengan external_id unik dari sistem Anda.
  5. Konfigurasikan webhook_url lewat PATCH /api/reseller/integration untuk menerima hasil final order secara real-time.
  6. Verifikasi X-Webhook-Signature di setiap request masuk menggunakan webhook_secret yang tersimpan.

Autentikasi

Semua endpoint memerlukan header X-API-Key dengan nilai API key aktif milik Anda.

HeaderWajibKeterangan
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
Selain header, API key juga diterima via query string ?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

MethodPathFungsi
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

FieldTipeWajibKeterangan
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
  }
}
Idempotency: Jika 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

StatusArti
queuedMenunggu diproses dalam antrean.
processingSedang diproses oleh vendor.
completedBerhasil. Produk telah dikirimkan ke target.
failedGagal. 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"
  }
}
Saldo dikurangi secara otomatis saat order berhasil dibuat. Jika order gagal, saldo akan dikembalikan (refund otomatis) beserta keterangan error di field 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"
  }
}
Simpan 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

Header request webhook

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"
}
Cocokkan orderId dari payload dengan orderId yang Anda simpan saat membuat order untuk mengetahui mana external_id yang selesai.

Retry otomatis

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

HTTPArtiTindakan
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.
Semua respons error berbentuk JSON:
{ "success": false, "error": "pesan error" }

Header rate limit

Checklist keamanan