Skip to content

Webhook ​

X-Guard memberi tahu server aplikasi saat data pengguna organisasi berubah. Pasang di rincian aplikasi → panel Webhook: satu alamat https per aplikasi, pilih event, lalu simpan secret webhook (diawali xgwh_, tampil sekali). Event pengguna hanya dikirim ke aplikasi yang boleh dibuka pengguna itu.

Event ​

EventArtinyaYang biasanya dilakukan
user.createdAkun aktif pertama kali, termasuk undangan yang diterimaSiapkan akun lokal (opsional)
user.updatedNama atau email berubahPerbarui profil lokal
user.disabledDitangguhkan atau dinonaktifkan adminTangguhkan akun lokal dan cabut semua sesinya
user.enabledAktif kembaliPulihkan, hanya bila penangguhan berasal dari X-Guard
user.deletedDihapus dari organisasiTangguhkan atau hapus akun lokal, cabut sesinya
user.apps_changedDitambah ke ("access": "granted") atau dikeluarkan dari ("revoked") daftar akses aplikasi iniUntuk revoked: cabut sesinya
webhook.pingUji kirim dari konsol, tanpa subJawab 2xx

Kiriman ​

http
POST <alamat webhook>
Content-Type: application/json
User-Agent: x-guard-webhook/1
X-XGuard-Event-Id: 6f1c0d2e-…            (sama dengan field id)
X-XGuard-Event-Type: user.disabled
X-XGuard-Timestamp: 1794200000
X-XGuard-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + body)>

{"email":"[email protected]","id":"6f1c0d2e-…","occurred_at":"2026-11-09T08:00:00.000000Z","sub":"USR_…","type":"user.disabled"}

Aturan penerima ​

  1. Hitung HMAC-SHA256 atas body mentah (jangan parse lalu serialisasi ulang), dengan secret webhook sebagai kunci dan timestamp + "." + body sebagai isi. Bandingkan dengan X-XGuard-Signature secara waktu-konstan.
  2. Tolak timestamp yang selisihnya lebih dari 5 menit dari jam server (cegah pemutaran ulang).
  3. Simpan id yang sudah diproses dan abaikan pengulangan, tetapi tetap jawab 2xx.
  4. sub yang tidak dikenal aplikasi: abaikan, jawab 2xx.
  5. Jawab 2xx dalam 10 detik. Selain itu X-Guard mencoba ulang dengan jeda 1 menit, 5 menit, 30 menit, 2 jam, 6 jam, dan 12 jam, sampai 24 jam sejak event terjadi, dengan id dan body yang sama.
  6. Event untuk pengguna yang sama dikirim berurutan: event berikutnya menunggu yang sebelumnya selesai.

Riwayat pengiriman (terkirim, menunggu, gagal, dan jawaban terakhir aplikasi) terlihat di panel Webhook. Uji kirim mengirim webhook.ping.

Verifikasi ​

Node.js

js
import { createHmac, timingSafeEqual } from "node:crypto";

// bodyMentah: Buffer body permintaan apa adanya (mis. express.raw({ type: "application/json" })).
export function verifikasiWebhook(secret, headers, bodyMentah) {
  const ts = headers["x-xguard-timestamp"];
  if (!/^\d+$/.test(ts ?? "") || Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error("timestamp");
  const harus = Buffer.from("sha256=" + createHmac("sha256", secret).update(`${ts}.`).update(bodyMentah).digest("hex"));
  const dapat = Buffer.from(headers["x-xguard-signature"] ?? "");
  if (harus.length !== dapat.length || !timingSafeEqual(harus, dapat)) throw new Error("tanda tangan");
  return JSON.parse(bodyMentah.toString("utf8"));
}

PHP

php
$ts = $request->header('X-XGuard-Timestamp');
$harus = 'sha256='.hash_hmac('sha256', $ts.'.'.$request->getContent(), env('XGUARD_WEBHOOK_SECRET'));
abort_unless(ctype_digit((string) $ts) && abs(time() - (int) $ts) <= 300
    && hash_equals($harus, (string) $request->header('X-XGuard-Signature')), 401);
$event = json_decode($request->getContent(), true);

Python

python
import hashlib, hmac, json, time

def verifikasi_webhook(secret: str, headers: dict, body: bytes) -> dict:
    ts = headers.get("X-XGuard-Timestamp", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        raise ValueError("timestamp")
    harus = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(harus, headers.get("X-XGuard-Signature", "")):
        raise ValueError("tanda tangan")
    return json.loads(body)

Go: xguard.WebhookHandler(secret, fn) di SDK Go. Laravel: XGuardSso::verifyWebhook di SDK Laravel. ASP Classic: webhook.asp di SDK ASP Classic.