REST API v1 · Sinkron dengan fitur terbaru

Dokumentasi API PanelH2H

Gunakan REST API PanelH2H untuk membaca katalog dan harga sesuai level akun, mengecek saldo, membuat transaksi produk Digiflazz maupun Provider Manual, menerima refund saldo otomatis pada kegagalan yang eligible, dan menerima status final melalui webhook/callback HMAC-SHA256.

Member · VIP · Reseller SKU Lokal A2H Saldo Akun Request HMAC-SHA256 Webhook HMAC-SHA256 120 request/menit
Base URLhttps://panelh2h.com/api/v1

Pengenalan

REST API PanelH2H hanya dapat digunakan menggunakan credential akun yang sudah dibuat dari halaman Setting. API Key terikat ke satu akun dan server mengambil level harga langsung dari akun tersebut.

Harga level otomatis. Akun Member menerima harga Member, VIP menerima harga VIP, dan Reseller menerima harga Reseller. Client tidak dapat memilih level melalui request.

Transaksi API menggunakan Saldo Akun. Client tidak mengirim harga. Server menghitung ulang harga efektif saat order dibuat, mengalikan dengan quantity, lalu memotong saldo secara transaksional.

Gunakan SKU lokal dari API. Website client menggunakan SKU lokal PanelH2H seperti A2H-.... SKU asli Digiflazz tetap menjadi mapping internal provider.

Quick Start

  1. Buat API Key + Secret Key di Akun Saya → Setting → REST API H2H.
  2. Tekan Tes Koneksi API sampai autentikasi berhasil.
  3. Isi URL callback, aktifkan webhook, simpan, lalu tekan Kirim Tes Webhook.
  4. Ambil katalog melalui GET /products dan gunakan SKU lokal serta harga dari response.
  5. Saat user membeli, kirim POST /transaction dengan ref_id unik.
  6. Simpan ref_id dan invoice response.
  7. Gunakan webhook transaction.updated sebagai jalur utama status final.
  8. Jika callback belum diterima, gunakan endpoint status sebagai fallback/reconciliation.
Webhook sudah tersedia. Polling bukan lagi mekanisme utama. Webhook dipakai untuk status final SUCCESS, FAILED, dan PARTIAL; polling tetap tersedia sebagai cadangan.

1. Credential API & Setting

Setiap akun mempunyai satu credential API. Membuat atau meregenerate credential menghasilkan API Key dan Secret Key baru. Secret Key hanya ditampilkan ketika dibuat atau diregenerate.

1LoginGunakan akun Member, VIP, atau Reseller.
2Buat CredentialBuka Setting lalu buat API Key + Secret Key.
3Simpan SecretSimpan Secret Key di backend atau environment variable.
4Tes KoneksiTes tidak membuat transaksi dan tidak memotong saldo.

Fitur yang tersedia pada Setting

  • Tes Koneksi API untuk memvalidasi API Key, Secret Key, HMAC, akun, level, saldo, dan akses produk.
  • Aktifkan / Nonaktifkan API. Credential nonaktif akan ditolak oleh endpoint API v1.
  • Regenerate API Key & Secret. URL webhook tetap tersimpan, tetapi Secret Key berubah sehingga verifier webhook client juga harus diperbarui.
  • Webhook / Callback REST API untuk menyimpan URL, mengaktifkan callback, mengirim tes, melihat delivery terakhir, dan retry manual delivery FAILED.
Secret Key jangan ditaruh di browser. HMAC harus dibuat dari backend/server website client.

2. Autentikasi Request HMAC-SHA256

Semua endpoint di bawah /api/v1 memakai tiga header berikut.

HeaderIsiKeterangan
X-API-KeyAPI Key akunCredential publik akun API.
X-TimestampUnix timestamp detikMaksimal selisih ±5 menit dari waktu server.
X-SignatureHMAC-SHA256 hexSignature canonical request menggunakan Secret Key.

Canonical request

Canonical
timestamp + "\n" + METHOD + "\n" + REQUEST_URI + "\n" + RAW_BODY

METHOD harus uppercase. REQUEST_URI adalah path + query string persis seperti request yang dikirim, tanpa domain. RAW_BODY harus byte/string JSON yang sama dengan yang benar-benar dikirim.

PHP · GET products
<?php
$apiKey = 'A2H-API-KEY-ANDA';
$secret = 'SECRET-KEY-ANDA';
$timestamp = (string) time();
$method = 'GET';
$uri = '/api/v1/products?limit=10';
$body = '';

$canonical = $timestamp."\n".$method."\n".$uri."\n".$body;
$signature = hash_hmac('sha256', $canonical, $secret);
cURL
curl -X GET 'https://panelh2h.com/api/v1/products?limit=10' \
  -H 'Accept: application/json' \
  -H 'X-API-Key: A2H-API-KEY-ANDA' \
  -H 'X-Timestamp: UNIX_TIMESTAMP' \
  -H 'X-Signature: HASIL_HMAC_SHA256'

Contoh POST

PHP
<?php
$timestamp = (string) time();
$method = 'POST';
$uri = '/api/v1/transaction';
$body = json_encode([
    'ref_id' => 'ORDER-CLIENT-001',
    'sku' => 'A2H-ML5',
    'customer_id' => '123456789',
    'server_id' => '1234',
    'quantity' => 1,
], JSON_UNESCAPED_SLASHES);

$canonical = $timestamp."\n".$method."\n".$uri."\n".$body;
$signature = hash_hmac('sha256', $canonical, $secret);
// Kirim $body yang sama persis sebagai raw request body.
Jika JSON dibuat ulang setelah signature dihitung, signature dapat menjadi berbeda. Buat raw body sekali, hitung HMAC dari raw body itu, lalu kirim raw body yang sama.

3. Daftar Endpoint API v1

MethodEndpointFungsi
GET/accountData User ID, nama, level, saldo, currency.
GET/balanceSaldo dan level akun terbaru.
GET/productsKatalog aktif dengan harga sesuai level.
GET/products/{sku}Detail satu produk berdasarkan SKU lokal.
POST/transactionMembuat transaksi menggunakan Saldo Akun.
POST/transaction/statusCek status menggunakan ref_id.
GET/transactions/{ref_id}Alternatif cek status menggunakan ref_id.
Rate limit API v1: 120 request/menit. Webhook mengurangi kebutuhan polling berulang. Jika melewati batas, server dapat mengembalikan HTTP 429.

4. Account & Balance

GET/account
Response 200
{
  "success": true,
  "data": {
    "user_id": "USR123456",
    "name": "Nama Akun",
    "level": "Reseller",
    "balance": 250000,
    "currency": "IDR"
  }
}
GET/balance
Response 200
{
  "success": true,
  "data": {
    "level": "VIP",
    "balance": 125000,
    "currency": "IDR"
  }
}

Saldo ini adalah Saldo Akun yang sama dengan saldo pada website PanelH2H. Transaksi API memotong saldo ini dan refund yang eligible mengkredit saldo yang sama.

5. Produk, SKU & Harga Level

GET/products
QueryTipeAturan
q opsionalstringMencari nama produk atau SKU.
category opsionalstringFilter kategori game.
limit opsionalinteger1–100, default 50.
page opsionalintegerHalaman pagination.
Contoh response
{
  "success": true,
  "level": "Reseller",
  "data": [
    {
      "sku": "A2H-ML5",
      "name": "Mobile Legends 5 Diamond",
      "game": "Mobile Legends",
      "game_slug": "mobile-legends",
      "category": "Game",
      "nominal": "5 Diamond",
      "price": 1500,
      "level": "Reseller",
      "currency": "IDR",
      "status": "available",
      "requires_user_id": true,
      "requires_server_id": true,
      "user_id_label": "User ID",
      "server_id_label": "Server ID"
    }
  ],
  "pagination": {
    "current_page": 1,
    "last_page": 10,
    "per_page": 50,
    "total": 500
  }
}
Harga berasal dari level pemilik API Key. Jangan mengirim level atau harga di request transaksi. Harga final dihitung lagi oleh server ketika order dibuat.
Produk yang tampil harus aktif dan game-nya juga aktif. Jika katalog disimpan di database client, sinkronkan berkala karena harga/status produk dapat berubah.
GET/products/{sku}

Mengambil satu produk berdasarkan SKU lokal. SKU tidak ditemukan, produk nonaktif, atau game nonaktif menghasilkan PRODUCT_NOT_FOUND HTTP 404.

6. Membuat Transaksi

POST/transaction

Order dibuat menggunakan Saldo Akun. Server mengunci saldo user, menghitung harga level × quantity, memotong saldo, membuat invoice A2H..., lalu menjadwalkan proses provider.

FieldTipeAturan
ref_id wajibstring3–80 karakter; huruf, angka, titik, underscore, titik dua, minus.
sku wajibstringSKU lokal dari endpoint produk, max 100 karakter.
customer_idstringWajib jika requires_user_id=true, max 100.
server_idstringWajib jika requires_server_id=true, max 100.
quantity opsionalintegerDefault 1. Maksimum mengikuti konfigurasi checkout dan hard limit tidak melebihi 50.

Jangan mengirim price, level, provider, provider_sku, atau callback_url. Nilai tersebut ditentukan server/Setting.

Request body
{
  "ref_id": "ORDER-CLIENT-0001",
  "sku": "A2H-ML5",
  "customer_id": "123456789",
  "server_id": "1234",
  "quantity": 1
}
Response 201
{
  "success": true,
  "idempotent": false,
  "data": {
    "ref_id": "ORDER-CLIENT-0001",
    "invoice": "A2H260819015100ABCDE",
    "status": "waiting",
    "order_status": "paid",
    "payment_status": "paid",
    "sku": "A2H-ML5",
    "product_name": "Mobile Legends 5 Diamond",
    "game": "Mobile Legends",
    "customer_id": "123456789",
    "server_id": "1234",
    "quantity": 1,
    "unit_price": 1500,
    "total": 1500,
    "currency": "IDR",
    "serial_number": [],
    "message": null,
    "created_at": "2026-08-19T01:51:00+07:00",
    "updated_at": "2026-08-19T01:51:00+07:00"
  }
}

Idempotency ref_id

ref_id unik per akun API. Jika request diulang dengan ref_id yang sudah ada, server mengembalikan order sebelumnya dengan idempotent: true dan tidak membuat debit saldo kedua.

Jika request pertama timeout, retry menggunakan ref_id yang sama. Jangan membuat ref_id baru hanya karena client tidak menerima response pertama.

Transaksi sedang dibuat paralel

Jika dua request dengan ref_id sama masuk bersamaan, salah satunya dapat menerima HTTP 409 TRANSACTION_BUSY. Tunggu sebentar lalu retry menggunakan ref_id yang sama.

Saldo tidak mencukupi

Jika saldo lebih kecil dari total server, request ditolak HTTP 422 dan debit tidak dilakukan. Validation error mengikuti format Laravel, misalnya:

Contoh 422
{
  "message": "Saldo tidak mencukupi. Saldo Rp 1.000 dan total transaksi Rp 1.500.",
  "errors": {
    "balance": [
      "Saldo tidak mencukupi. Saldo Rp 1.000 dan total transaksi Rp 1.500."
    ]
  }
}

7. Cek Status & Lifecycle

Webhook adalah jalur utama status final. Endpoint status digunakan untuk fallback, pengecekan manual, dan rekonsiliasi.

POST/transaction/status
Body
{ "ref_id": "ORDER-CLIENT-0001" }
GET/transactions/{ref_id}

Kedua endpoint hanya dapat membaca transaksi REST API milik akun pemilik API Key tersebut.

Nilai data.status yang digunakan

waitingOrder baru dan menunggu proses provider.
submittingSistem sedang mengirim transaksi ke provider.
processingProvider sedang memproses atau mengembalikan status non-final.
retryingKendala sementara; job akan mencoba kembali. Belum final dan belum direfund.
manual_pendingProvider Manual menunggu keputusan admin.
unmappedProduk tidak memiliki provider/SKU mapping yang valid. Ini masalah konfigurasi dan bukan status final webhook.
success / completedFinal berhasil. Memicu transaction.updated jika webhook aktif.
failed / cancelledFinal gagal/dibatalkan. Memicu transaction.updated jika webhook aktif.
partialMulti-order final dengan sebagian unit sukses dan sebagian gagal.
unmapped bukan SUCCESS/FAILED final. Client dapat melihat status ini lewat polling dan sebaiknya menghubungi pengelola PanelH2H karena produk membutuhkan perbaikan mapping provider.

Serial number

serial_number berupa string jika hanya satu SN tersedia, array jika ada beberapa SN pada multi-order, dan [] jika belum/tidak ada SN.

8. Produk Digiflazz & Provider Manual

Digiflazz

Client tetap menggunakan SKU lokal PanelH2H. Server membaca provider_sku internal, membentuk tujuan berdasarkan customer_id/server_id dan separator game, lalu mengirim order ke Digiflazz. Status non-final Digiflazz dipetakan menjadi processing.

Untuk quantity > 1, unit diproses dengan reference internal per unit. Hasil akhir order dapat menjadi success, failed, atau partial.

Provider Manual

Produk Manual juga dapat di-hit melalui REST API selama produk dan game aktif. Setelah debit saldo dan order dibuat, provider status menjadi manual_pending dan admin memprosesnya dari dashboard.

  • Admin menekan SUKSES → order menjadi success dan callback final dapat dikirim.
  • Admin dapat mengisi serial number; jika kosong, invoice order dipakai sebagai SN default.
  • Admin menekan GAGAL → order menjadi failed, refund diproses bila memenuhi syarat, lalu callback final dapat dikirim.

9. Refund Saldo Otomatis

REST API selalu mencatat pembayaran sebagai Saldo Akun. Karena itu kegagalan final yang diproses oleh alur Digiflazz atau Provider Manual dapat dikreditkan kembali ke saldo user secara otomatis.

  • Single order Digiflazz FAILED → subtotal yang dipotong direfund otomatis.
  • Seluruh retry job habis → order menjadi failed lalu refund otomatis dijalankan.
  • Provider Manual GAGAL → subtotal direfund otomatis.
  • Multi-order PARTIAL → hanya harga unit yang gagal yang direfund; unit sukses tetap terjual.
  • Status waiting, submitting, processing, retrying, dan manual_pending belum final sehingga belum direfund.
REST API tidak menambahkan fee Tripay/payment gateway. Nilai maksimal refund adalah nilai yang memang dipotong dari Saldo Akun untuk subtotal atau unit gagal.
Refund dilindungi reference unik dan total credit dibatasi agar tidak melebihi nilai maksimal yang eligible, sehingga retry/refund event tidak seharusnya menggandakan saldo.

Payload REST status maupun webhook tidak menjanjikan field refund khusus. Jika client perlu merekonsiliasi saldo setelah kegagalan, gunakan GET /balance.

10. Webhook / Callback

Webhook mengirim HTTP POST ke satu URL callback yang tersimpan pada credential akun. Webhook transaksi dibuat ketika order REST API berubah ke status terminal dan webhook akun sedang aktif.

Aktifkan webhook sebelum transaksi mencapai status final. Jika webhook sedang nonaktif ketika order menjadi final, delivery historis tidak dibuat otomatis hanya karena webhook diaktifkan kemudian.

Aturan URL callback

  • Wajib HTTPS dan port 443.
  • Username/password di URL tidak diizinkan.
  • Localhost, private IP, loopback, link-local, dan reserved IP diblokir.
  • Hostname harus dapat di-resolve ke IP publik.
Contoh
https://client.example.com/api/panelh2h/callback

Event

transaction.updatedDikirim untuk terminal success/completed, failed/cancelled, atau partial.
webhook.testDikirim saat tombol Kirim Tes Webhook ditekan.

Header callback

HeaderIsiKeterangan
X-PanelH2H-KeyAPI KeyAPI Key credential pengirim.
X-PanelH2H-EventNama eventtransaction.updated atau webhook.test.
X-PanelH2H-DeliveryUUIDUnique delivery ID untuk deduplikasi.
X-PanelH2H-TimestampUnix timestampWaktu pengiriman callback.
X-PanelH2H-SignatureHMAC-SHA256 hexSignature menggunakan Secret Key API yang sama.

Canonical callback

Berbeda dari canonical request API. Callback hanya menandatangani timestamp + newline + raw body.
Canonical callback
timestamp + "\n" + RAW_BODY

Contoh verifier PHP

PHP
<?php
$secret = getenv('PANELH2H_SECRET');
$timestamp = $_SERVER['HTTP_X_PANELH2H_TIMESTAMP'] ?? '';
$signature = strtolower($_SERVER['HTTP_X_PANELH2H_SIGNATURE'] ?? '');
$deliveryId = $_SERVER['HTTP_X_PANELH2H_DELIVERY'] ?? '';
$apiKey = $_SERVER['HTTP_X_PANELH2H_KEY'] ?? '';
$rawBody = file_get_contents('php://input');

if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit('timestamp invalid');
}

$expected = hash_hmac('sha256', $timestamp."\n".$rawBody, $secret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('signature invalid');
}

$payload = json_decode($rawBody, true);
if (!is_array($payload)) {
    http_response_code(400);
    exit('invalid json');
}

// Pastikan $apiKey sama dengan API Key yang Anda simpan.
// Jadikan $deliveryId UNIQUE agar retry tidak diproses dua kali.
// Update transaksi lokal berdasarkan $payload['data']['ref_id'].

http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['success' => true]);

Payload transaction.updated

JSON
{
  "event": "transaction.updated",
  "delivery_id": "2c56b41f-4adb-4fd7-8ac8-f6bcaa378501",
  "occurred_at": "2026-08-19T01:52:00+07:00",
  "data": {
    "ref_id": "ORDER-CLIENT-0001",
    "invoice": "A2H260819015100ABCDE",
    "status": "success",
    "order_status": "success",
    "payment_status": "paid",
    "sku": "A2H-ML5",
    "product_name": "Mobile Legends 5 Diamond",
    "game": "Mobile Legends",
    "customer_id": "123456789",
    "server_id": "1234",
    "quantity": 1,
    "unit_price": 1500,
    "total": 1500,
    "currency": "IDR",
    "serial_number": "SN123456789",
    "message": "Transaksi Berhasil",
    "created_at": "2026-08-19T01:51:00+07:00",
    "updated_at": "2026-08-19T01:51:59+07:00"
  }
}

Payload webhook.test

JSON
{
  "event": "webhook.test",
  "delivery_id": "UUID-DELIVERY",
  "occurred_at": "2026-08-19T01:52:00+07:00",
  "data": {
    "message": "Webhook PanelH2H berhasil terhubung jika payload ini diterima.",
    "user_id": "USR123456",
    "level": "VIP"
  }
}

Response & timeout

Callback dianggap berhasil jika endpoint client membalas HTTP 200–299. Redirect tidak diikuti. Connect timeout sekitar 5 detik dan total HTTP timeout sekitar 10 detik, jadi endpoint client sebaiknya menyimpan event dengan cepat lalu langsung membalas 2xx.

Retry otomatis

Total maksimum 6 attempt: pengiriman pertama langsung, lalu backoff sekitar 30 detik, 60 detik, 2 menit, 5 menit, dan 10 menit.

pendingDelivery sudah dibuat dan menunggu worker.
processingSedang dikirim ke endpoint client.
retryingAttempt gagal dan akan dicoba kembali.
deliveredClient membalas HTTP 2xx.
failedSeluruh attempt habis atau webhook/credential tidak tersedia.
Client harus idempotent. Simpan X-PanelH2H-Delivery sebagai unique key. Jika delivery yang sama diterima lagi, jangan memproses perubahan dua kali; cukup balas 2xx.

Log & retry manual

Halaman Setting menampilkan delivery terbaru beserta event, Delivery ID, status, jumlah attempt, HTTP status terakhir, waktu dibuat, dan error terakhir. Delivery FAILED dapat dijadwalkan ulang menggunakan tombol Retry.

11. Error & HTTP Status

HTTPKodeArti
401AUTH_MISSINGHeader autentikasi tidak lengkap.
401AUTH_TIMESTAMP_INVALIDTimestamp bukan Unix timestamp detik.
401AUTH_TIMESTAMP_EXPIREDSelisih timestamp lebih dari 5 menit.
401AUTH_KEY_INVALIDAPI Key salah/nonaktif atau akun credential tidak tersedia.
401AUTH_SIGNATURE_INVALIDHMAC tidak cocok.
404PRODUCT_NOT_FOUNDSKU tidak ditemukan atau produk/game nonaktif.
422CUSTOMER_ID_REQUIREDProduk membutuhkan customer_id.
422SERVER_ID_REQUIREDProduk membutuhkan server_id.
409TRANSACTION_BUSYref_id yang sama sedang diproses paralel.
404TRANSACTION_NOT_FOUNDref_id tidak ditemukan untuk akun API tersebut.
422Validation ErrorInput invalid, quantity melebihi batas, ref_id invalid, atau saldo tidak cukup.
429Too Many RequestsRate limit terlampaui.
Error terstruktur
{
  "success": false,
  "code": "AUTH_SIGNATURE_INVALID",
  "message": "Signature HMAC tidak valid."
}

ValidationException Laravel dapat memakai format message + object errors; client sebaiknya menangani kedua format.

12. Checklist Integrasi Aman

  • Simpan Secret Key hanya di backend/environment variable.
  • Jangan menaruh Secret Key di JavaScript browser, aplikasi frontend publik, screenshot, atau repository Git.
  • Sinkronkan waktu server dan buat timestamp baru untuk setiap request.
  • Sign request URI dan raw body yang sama persis dengan request yang dikirim.
  • Verifikasi X-PanelH2H-Key, timestamp, dan signature sebelum memproses callback.
  • Jadikan X-PanelH2H-Delivery unique di database client.
  • Gunakan satu ref_id unik per order dan retry request timeout menggunakan ref_id yang sama.
  • Jangan hard-code harga atau memilih level sendiri; server adalah sumber harga final.
  • Jangan memakai SKU Digiflazz langsung. Gunakan SKU lokal dari endpoint produk.
  • Gunakan webhook sebagai jalur utama dan polling hanya sebagai fallback/reconciliation.
  • Jika credential bocor, regenerate API Key & Secret dan segera update signer request serta verifier callback.
Penyebab HMAC request paling umum gagal: query string berbeda, URI menyertakan domain, body JSON berubah setelah signing, METHOD tidak uppercase, timestamp kedaluwarsa, atau Secret Key sudah diregenerate.