Setup & Konfigurasi Webhook
1. Daftarkan URL Webhook
- Login ke Dashboard IsiKuota
- Buka Pengaturan → API → Webhook
- Masukkan URL endpoint server Anda (harus HTTPS di production)
- Klik Simpan
Contoh URL webhook:
https://yourdomain.com/webhook/isikuota
warning
URL webhook harus dapat diakses publik dari internet. Tidak bisa menggunakan localhost atau IP private di production.
2. Implementasi Handler Webhook
PHP (Native)
<?php
// webhook.php
$secret = $_ENV['ISIKUOTA_SECRET_KEY'];
$rawBody = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
// Verifikasi signature
$expected = hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $sigHeader)) {
http_response_code(401);
exit(json_encode(['error' => 'Invalid signature']));
}
$payload = json_decode($rawBody, true);
$event = $payload['event'];
$data = $payload['data'];
$refId = $data['ref_id'];
// Cek idempoten — jangan proses ulang yang sudah diproses
if (alreadyProcessed($refId)) {
http_response_code(200);
exit(json_encode(['status' => 'already_processed']));
}
// Proses berdasarkan event
switch ($event) {
case 'order.success':
updateOrderStatus($refId, 'success', $data['sn']);
sendNotificationToUser($refId);
break;
case 'order.failed':
updateOrderStatus($refId, 'failed');
sendFailureNotification($refId, $data['message']);
break;
case 'order.sn_received':
saveSN($refId, $data['sn']);
break;
}
// Selalu balas 200 dengan cepat
http_response_code(200);
echo json_encode(['status' => 'ok']);
PHP (Laravel)
<?php
// routes/api.php
Route::post('/webhook/isikuota', [WebhookController::class, 'handle']);
// app/Http/Controllers/WebhookController.php
class WebhookController extends Controller
{
public function handle(Request $request)
{
$secret = config('services.isikuota.secret');
$rawBody = $request->getContent();
$signature = $request->header('X-Signature');
if (!hash_equals(hash_hmac('sha256', $rawBody, $secret), $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
$payload = $request->json()->all();
// Dispatch ke job untuk proses di background
ProcessWebhook::dispatch($payload);
return response()->json(['status' => 'ok']);
}
}
// app/Jobs/ProcessWebhook.php
class ProcessWebhook implements ShouldQueue
{
public function handle(): void
{
$event = $this->payload['event'];
$data = $this->payload['data'];
match ($event) {
'order.success' => $this->handleSuccess($data),
'order.failed' => $this->handleFailed($data),
default => null,
};
}
}
JavaScript (Express.js)
const express = require('express');
const crypto = require('crypto');
const app = express();
// Gunakan raw body parser agar bisa verifikasi signature
app.use('/webhook', express.raw({ type: 'application/json' }));
app.post('/webhook/isikuota', (req, res) => {
const secret = process.env.ISIKUOTA_SECRET_KEY;
const rawBody = req.body;
const signature = req.headers['x-signature'];
// Verifikasi signature
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Balas 200 dulu, proses di background
res.status(200).json({ status: 'ok' });
const payload = JSON.parse(rawBody);
// Proses asinkron
processWebhook(payload).catch(console.error);
});
async function processWebhook(payload) {
const { event, data } = payload;
switch (event) {
case 'order.success':
await updateOrder(data.ref_id, 'success', data.sn);
break;
case 'order.failed':
await updateOrder(data.ref_id, 'failed');
break;
}
}
Python (Flask)
import hmac, hashlib, json
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET = 'your_secret_key'
@app.route('/webhook/isikuota', methods=['POST'])
def webhook():
raw_body = request.get_data()
signature = request.headers.get('X-Signature', '')
# Verifikasi signature
expected = hmac.new(
SECRET.encode(),
raw_body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
return jsonify({'error': 'Invalid signature'}), 401
payload = json.loads(raw_body)
event = payload['event']
data = payload['data']
# Balas 200 dulu
response = jsonify({'status': 'ok'})
# Proses di background (gunakan Celery/threading)
process_event.delay(event, data)
return response, 200
3. Test Webhook
Gunakan fitur Callback Tester di Dashboard untuk mengirim dummy payload ke URL webhook Anda:
- Dashboard → Pengaturan → API → Webhook
- Klik Test Webhook
- Pilih tipe event yang ingin ditest
- Klik Kirim dan periksa log server Anda
Checklist Setup Webhook
- URL webhook terdaftar di Dashboard
- Server dapat diakses via HTTPS
- Verifikasi
X-Signaturediimplementasikan - Menggunakan timing-safe comparison (
hash_equals/timingSafeEqual) - Server membalas HTTP 200 dalam 30 detik
- Proses berat dikerjakan di background/queue
- Implementasi idempoten (cek
ref_idsebelum proses) - Log setiap callback tersimpan