Skip to main content

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"
}
}
FieldKeterangan
eventTipe event (lihat Events)
timestampWaktu event dikirim
data.ref_idID referensi transaksi Anda
data.statusSuccess / Failed / Pending
data.snSerial 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

PersyaratanDetail
ProtokolHTTPS (wajib di production)
MethodPOST
ResponseHTTP 200 dalam 30 detik
Body responseBebas (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

  1. Selalu verifikasi X-Signature sebelum memproses payload
  2. Gunakan timing-safe comparison (hash_equals / timingSafeEqual) untuk mencegah timing attack
  3. Balas HTTP 200 segera, proses di background/queue
  4. Simpan log setiap callback untuk audit dan debugging
  5. Idempoten: cek ref_id sudah diproses sebelumnya sebelum update DB
  6. Test menggunakan fitur Callback Tester di Dashboard

Setup Webhook →Daftar Events →