Skip to main content

Setup & Konfigurasi Webhook

1. Daftarkan URL Webhook

  1. Login ke Dashboard IsiKuota
  2. Buka Pengaturan → API → Webhook
  3. Masukkan URL endpoint server Anda (harus HTTPS di production)
  4. 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:

  1. Dashboard → Pengaturan → API → Webhook
  2. Klik Test Webhook
  3. Pilih tipe event yang ingin ditest
  4. Klik Kirim dan periksa log server Anda

Checklist Setup Webhook

  • URL webhook terdaftar di Dashboard
  • Server dapat diakses via HTTPS
  • Verifikasi X-Signature diimplementasikan
  • Menggunakan timing-safe comparison (hash_equals / timingSafeEqual)
  • Server membalas HTTP 200 dalam 30 detik
  • Proses berat dikerjakan di background/queue
  • Implementasi idempoten (cek ref_id sebelum proses)
  • Log setiap callback tersimpan

Lihat daftar Events →