Webhook Overview
Webhook memungkinkan IsiKuota mengirim notifikasi real-time ke server Anda setiap kali status transaksi berubah — tanpa perlu polling.
Cara Kerja
Transaksi terjadi
│
▼
IsiKuota memproses
│
▼
Status berubah (Success/Failed)
│
▼
IsiKuota POST ke URL webhook Anda
│
▼
Server Anda membalas HTTP 200
Request Headers
Setiap request webhook dari IsiKuota menyertakan headers berikut:
Content-Type: application/json
X-Signature: <HMAC-SHA256 dari JSON body>
X-Type-Transaction: ppob
Payload Webhook
{
"event": "order.success",
"timestamp": "2025-01-15T14:30:00+07:00",
"data": {
"order_id": 12345,
"ref_id": "ORDER-2025-001",
"status": "Success",
"product_code": "TSEL10",
"product_name": "Telkomsel 10.000",
"customer_number": "081234567890",
"price": 11500,
"sn": "1234567890123456",
"message": "Transaksi berhasil",
"created_at": "2025-01-15T14:29:55+07:00",
"updated_at": "2025-01-15T14:30:00+07:00"
}
}
| Field | Keterangan |
|---|---|
event | Tipe event (lihat Events) |
timestamp | Waktu event dikirim |
data.ref_id | ID referensi transaksi Anda |
data.status | Success / Failed / Pending |
data.sn | Serial number (kosong jika belum ada) |
Verifikasi Signature
Wajib verifikasi setiap request webhook menggunakan X-Signature untuk memastikan request benar-benar dari IsiKuota.
Formula:
X-Signature = hash_hmac('sha256', json_encode(payload), api_secret)
PHP
<?php
function verifyWebhook(string $rawBody, string $signature, string $secret): bool
{
$expected = hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signature);
}
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if (!verifyWebhook($rawBody, $signature, 'your_secret_key')) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true);
// Proses webhook...
http_response_code(200);
echo json_encode(['status' => 'ok']);
JavaScript (Node.js / Express)
const crypto = require('crypto');
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature'];
if (!verifyWebhook(req.body, signature, process.env.API_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = JSON.parse(req.body);
// Proses webhook...
res.status(200).json({ status: 'ok' });
});
Python (Flask)
import hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
def verify_webhook(raw_body, signature, secret):
expected = hmac.new(
secret.encode(),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhook', methods=['POST'])
def webhook():
raw_body = request.get_data()
signature = request.headers.get('X-Signature', '')
if not verify_webhook(raw_body, signature, 'your_secret_key'):
return jsonify({'error': 'Invalid signature'}), 401
payload = request.get_json()
# Proses webhook...
return jsonify({'status': 'ok'}), 200
Persyaratan Server Webhook
| Persyaratan | Detail |
|---|---|
| Protokol | HTTPS (wajib di production) |
| Method | POST |
| Response | HTTP 200 dalam 30 detik |
| Body response | Bebas (diabaikan IsiKuota) |
warning
Jika server tidak merespons dalam 30 detik atau mengembalikan status bukan 2xx, IsiKuota akan retry pengiriman hingga 3 kali dengan jeda 1 menit.
Best Practices
- Selalu verifikasi
X-Signaturesebelum memproses payload - Gunakan timing-safe comparison (
hash_equals/timingSafeEqual) untuk mencegah timing attack - Balas HTTP 200 segera, proses di background/queue
- Simpan log setiap callback untuk audit dan debugging
- Idempoten: cek
ref_idsudah diproses sebelumnya sebelum update DB - Test menggunakan fitur Callback Tester di Dashboard