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.
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.
A2H-.... SKU asli Digiflazz tetap menjadi mapping internal provider.Quick Start
- Buat API Key + Secret Key di Akun Saya → Setting → REST API H2H.
- Tekan Tes Koneksi API sampai autentikasi berhasil.
- Isi URL callback, aktifkan webhook, simpan, lalu tekan Kirim Tes Webhook.
- Ambil katalog melalui
GET /productsdan gunakan SKU lokal serta harga dari response. - Saat user membeli, kirim
POST /transactiondenganref_idunik. - Simpan
ref_iddaninvoiceresponse. - Gunakan webhook
transaction.updatedsebagai jalur utama status final. - Jika callback belum diterima, gunakan endpoint status sebagai fallback/reconciliation.
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.
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.
2. Autentikasi Request HMAC-SHA256
Semua endpoint di bawah /api/v1 memakai tiga header berikut.
| Header | Isi | Keterangan |
|---|---|---|
X-API-Key | API Key akun | Credential publik akun API. |
X-Timestamp | Unix timestamp detik | Maksimal selisih ±5 menit dari waktu server. |
X-Signature | HMAC-SHA256 hex | Signature canonical request menggunakan Secret Key. |
Canonical request
timestamp + "\n" + METHOD + "\n" + REQUEST_URI + "\n" + RAW_BODYMETHOD 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
$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 -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
$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.3. Daftar Endpoint API v1
| Method | Endpoint | Fungsi |
|---|---|---|
| GET | /account | Data User ID, nama, level, saldo, currency. |
| GET | /balance | Saldo dan level akun terbaru. |
| GET | /products | Katalog aktif dengan harga sesuai level. |
| GET | /products/{sku} | Detail satu produk berdasarkan SKU lokal. |
| POST | /transaction | Membuat transaksi menggunakan Saldo Akun. |
| POST | /transaction/status | Cek status menggunakan ref_id. |
| GET | /transactions/{ref_id} | Alternatif cek status menggunakan ref_id. |
4. Account & Balance
{
"success": true,
"data": {
"user_id": "USR123456",
"name": "Nama Akun",
"level": "Reseller",
"balance": 250000,
"currency": "IDR"
}
}{
"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
| Query | Tipe | Aturan |
|---|---|---|
q opsional | string | Mencari nama produk atau SKU. |
category opsional | string | Filter kategori game. |
limit opsional | integer | 1–100, default 50. |
page opsional | integer | Halaman pagination. |
{
"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
}
}Mengambil satu produk berdasarkan SKU lokal. SKU tidak ditemukan, produk nonaktif, atau game nonaktif menghasilkan PRODUCT_NOT_FOUND HTTP 404.
6. Membuat Transaksi
Order dibuat menggunakan Saldo Akun. Server mengunci saldo user, menghitung harga level × quantity, memotong saldo, membuat invoice A2H..., lalu menjadwalkan proses provider.
| Field | Tipe | Aturan |
|---|---|---|
ref_id wajib | string | 3–80 karakter; huruf, angka, titik, underscore, titik dua, minus. |
sku wajib | string | SKU lokal dari endpoint produk, max 100 karakter. |
customer_id | string | Wajib jika requires_user_id=true, max 100. |
server_id | string | Wajib jika requires_server_id=true, max 100. |
quantity opsional | integer | Default 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.
{
"ref_id": "ORDER-CLIENT-0001",
"sku": "A2H-ML5",
"customer_id": "123456789",
"server_id": "1234",
"quantity": 1
}{
"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.
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:
{
"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.
{ "ref_id": "ORDER-CLIENT-0001" }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.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.
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.
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.
https://client.example.com/api/panelh2h/callbackEvent
transaction.updatedDikirim untuk terminal success/completed, failed/cancelled, atau partial.webhook.testDikirim saat tombol Kirim Tes Webhook ditekan.Header callback
| Header | Isi | Keterangan |
|---|---|---|
X-PanelH2H-Key | API Key | API Key credential pengirim. |
X-PanelH2H-Event | Nama event | transaction.updated atau webhook.test. |
X-PanelH2H-Delivery | UUID | Unique delivery ID untuk deduplikasi. |
X-PanelH2H-Timestamp | Unix timestamp | Waktu pengiriman callback. |
X-PanelH2H-Signature | HMAC-SHA256 hex | Signature menggunakan Secret Key API yang sama. |
Canonical callback
timestamp + "\n" + RAW_BODYContoh verifier 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
{
"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
{
"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.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
| HTTP | Kode | Arti |
|---|---|---|
| 401 | AUTH_MISSING | Header autentikasi tidak lengkap. |
| 401 | AUTH_TIMESTAMP_INVALID | Timestamp bukan Unix timestamp detik. |
| 401 | AUTH_TIMESTAMP_EXPIRED | Selisih timestamp lebih dari 5 menit. |
| 401 | AUTH_KEY_INVALID | API Key salah/nonaktif atau akun credential tidak tersedia. |
| 401 | AUTH_SIGNATURE_INVALID | HMAC tidak cocok. |
| 404 | PRODUCT_NOT_FOUND | SKU tidak ditemukan atau produk/game nonaktif. |
| 422 | CUSTOMER_ID_REQUIRED | Produk membutuhkan customer_id. |
| 422 | SERVER_ID_REQUIRED | Produk membutuhkan server_id. |
| 409 | TRANSACTION_BUSY | ref_id yang sama sedang diproses paralel. |
| 404 | TRANSACTION_NOT_FOUND | ref_id tidak ditemukan untuk akun API tersebut. |
| 422 | Validation Error | Input invalid, quantity melebihi batas, ref_id invalid, atau saldo tidak cukup. |
| 429 | Too Many Requests | Rate limit terlampaui. |
{
"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-Deliveryunique di database client. - Gunakan satu
ref_idunik 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.