Webhook
Menerima callback status order dan memverifikasi tanda tangan HMAC.
Saat status invoice mencapai kondisi final (misalnya SUCCESS, FAILED, REFUNDED), sistem mengirim HTTP POST ke URL callback Anda.
Prioritas URL callback
callback_urlpada request order- Callback default di pengaturan akses H2H Anda
- Callback default akun (fallback)
Headers
| Header | Arti |
|---|---|
X-Webhook-Event | Jenis event, contoh invoice.status_changed |
X-Webhook-Id | ID pengiriman / job |
X-Webhook-Timestamp | Unix timestamp (detik) |
X-Tenant-Id | ID akun |
X-Tenant-Member-Id | ID member Anda |
X-Webhook-Signature | HMAC-SHA256 hex dari timestamp + "." + raw_body |
Signature dihitung memakai API secret akses H2H Anda.
Contoh body
{
"event": "invoice.status_changed",
"data": {
"invoice_number": "API123...",
"partner_ref": "ORDER-1001",
"status": "SUCCESS",
"amount": 10000,
"target": "123(456)",
"serial_number": "...",
"member_id": "...",
"member_role": "RESELLER",
"updated_at": "2026-07-21T10:00:00.000Z"
}
}Verifikasi signature
import crypto from 'node:crypto';
function verifyWebhook({ apiSecret, timestamp, signature, rawBody }) {
const expected = crypto
.createHmac('sha256', apiSecret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
if (expected !== signature) {
throw new Error('Invalid webhook signature');
}
}
// const timestamp = req.headers['x-webhook-timestamp'];
// const signature = req.headers['x-webhook-signature'];
// const rawBody = req.rawBody; // string JSON mentah
// verifyWebhook({ apiSecret, timestamp, signature, rawBody });function verifyWebhook(string $apiSecret, string $timestamp, string $signature, string $rawBody): void {
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $apiSecret);
if (!hash_equals($expected, $signature)) {
throw new RuntimeException('Invalid webhook signature');
}
}import hashlib, hmac
def verify_webhook(api_secret: str, timestamp: str, signature: str, raw_body: str) -> None:
expected = hmac.new(
api_secret.encode(),
f'{timestamp}.{raw_body}'.encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError('Invalid webhook signature')mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(timestamp + "." + rawBody))
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
return errors.New("invalid webhook signature")
}Tips implementasi
- Selalu verifikasi signature sebelum memproses event
- Gunakan raw body persis seperti yang diterima
- Buat endpoint webhook idempotent — event yang sama bisa dikirim ulang
- Balas cepat dengan
2xx; proses berat sebaiknya di-queue - Simpan
partner_ref/invoice_numbersebagai kunci update di database Anda