Webhook mengirimkan peristiwa identifikasi ke server Anda secara real-time. Setiap
kali seorang pengunjung diidentifikasi, TRACIO mengirimkan permintaan HTTP POST
ke URL webhook yang telah Anda konfigurasikan. Body permintaan merupakan payload
peristiwa itu sendiri — tidak ada envelope pembungkus.
Body-nya adalah dokumen JSON datar dalam format camelCase. Ini adalah subset publik
terkurasi dari peristiwa internal — tanpa hash fingerprint atau penanda deteksi
internal.
{ "requestId": "1710432000_abc123def", "phase": "primary", "visitorId": "X7fh2Hg9LkMn3pQr", "linkedId": "user_12345", "tag": "login", "timestamp": "2024-03-12T16:00:00Z", "url": "https://your-app.com/login", "ip": "94.142.239.124", "userAgent": "Mozilla/5.0 …", "browser": { "name": "Chrome", "version": "120.0" }, "os": { "name": "macOS", "version": "14.3" }, "device": "desktop", "geo": { "country": "CZ", "city": "Prague", "lat": 50.05, "lon": 14.4, "timezone": "Europe/Prague", "isp": "Example ISP" }, "network": { "vpn": false, "proxy": false, "tor": false, "datacenter": false, "connectionType": "residential" }, "bot": { "result": "human", "type": "", "score": 0.02 }, "identification": { "confidence": 0.95, "incognito": false, "visitType": "returning" }, "decision": { "action": "allow", "riskScore": 4, "suspectScore": 0.08 }}| Field | Tipe | Deskripsi |
|---|---|---|
requestId | string | Pengenal peristiwa unik (sekaligus kunci idempotensi) |
phase | string | primary atau late — lihat di bawah |
visitorId | string | Pengenal pengunjung yang stabil |
linkedId | string | Pengenal tertaut yang disediakan oleh klien |
tag | string | Tag kustom yang disediakan oleh klien |
timestamp | string | Waktu peristiwa (RFC 3339) |
url | string | URL halaman tempat peristiwa ditangkap |
ip | string | Alamat IP klien |
userAgent | string | String user-agent klien mentah |
browser.name / .version | string | Browser yang terdeteksi |
os.name / .version | string | Sistem operasi yang terdeteksi |
device | string | Kelas perangkat (mis. desktop, mobile) |
geo | object | Geolokasi IP: country, city, lat, lon, timezone, isp |
network | object | vpn, proxy, tor, datacenter (boolean) dan connectionType |
bot.result | string | human, bot, atau uncertain |
bot.type | string | Label otomasi bebas ketika bot terdeteksi |
bot.score | number | Skor probabilitas bot |
identification.confidence | number | Keyakinan model (0.0–1.0) |
identification.incognito | boolean | Konteks penjelajahan privat/incognito |
identification.visitType | string | Klasifikasi kunjungan |
decision.action | string | Tindakan yang direkomendasikan |
decision.riskScore | number | Skor risiko agregat (0–100) |
decision.suspectScore | number | Skor kecurigaan berbutir halus |
phase membedakan dua pengiriman yang berbagi requestId yang sama:
primary — dikirim segera saat pengunjung diidentifikasi.late — peristiwa pengayaan lanjutan (verdikt bot yang telah disempurnakan
dan sinyal perilaku yang ditangkap sedikit lebih lambat).Korelasikan keduanya berdasarkan requestId dan bedakan berdasarkan phase.
Setiap pengiriman menyertakan header X-Tracio-Signature:
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed...t adalah timestamp Unix (detik) saat permintaan ditandatangani.v1 adalah HMAC-SHA256 berkode heksadesimal dari "<t>.<rawRequestBody>", dengan
webhook secret Anda sebagai kunci.Timestamp adalah bagian dari konten yang ditandatangani, yang memberikan perlindungan
replay — verifikasi terhadap Buffer body permintaan mentah (jangan gunakan JSON
yang telah diurai/diserialisasi ulang, atau tanda tangan tidak akan cocok). Bangun
konten yang ditandatangani sebagai byte: prefiks "<t>." digabungkan dengan buffer
body mentah, lalu lakukan HMAC atasnya.
// Express.js exampleimport express from "express"import crypto from "crypto"
const app = express()
// Capture the raw body so we can verify the signature byte-for-byte.app.use( express.json({ verify: (req, _res, buf) => { ;(req as any).rawBody = buf }, }),)
function verifySignature(rawBody: Buffer, header: string, secret: string): boolean { // Parse "t=...,v1=..." const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=") as [string, string])) const t = parts["t"] const v1 = parts["v1"] if (!t || !v1) return false
// Sign the raw bytes: "<t>." prefix + the raw request body buffer. const signed = Buffer.concat([Buffer.from(`${t}.`, "utf8"), rawBody]) const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex")
const a = Buffer.from(v1, "hex") const b = Buffer.from(expected, "hex") return a.length === b.length && crypto.timingSafeEqual(a, b)}
app.post("/webhook/tracio", (req, res) => { const header = req.headers["x-tracio-signature"] as string if (!verifySignature((req as any).rawBody, header, WEBHOOK_SECRET)) { return res.status(401).json({ error: "Invalid signature" }) }
const event = req.body console.log(`Visitor: ${event.visitorId}`) console.log(`Bot: ${event.bot.result}`) // "human" | "bot" | "uncertain"
res.status(200).send("OK")})Anda mungkin juga ingin menolak pengiriman yang t-nya terlalu jauh dari waktu saat
ini (misalnya, lebih dari lima menit selisih) sebagai perlindungan replay tambahan.
| Header | Deskripsi |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | t=<unix>,v1=<hmac_sha256_hex> atas "<t>.<rawBody>" |
X-Tracio-Event-Id | requestId peristiwa — gunakan sebagai kunci idempotensi |
X-Tracio-Webhook-Id | Pengenal webhook yang menghasilkan pengiriman ini |
Cara paling sederhana untuk mengelola webhook adalah secara visual di dashboard.
Webhook juga dapat dikelola secara terprogram melalui management API bercakupan
workspace — API yang sama yang digunakan dashboard. API ini disajikan pada host
aplikasi (misalnya https://app.tracio.ai/api/v1) dan diautentikasi dengan JWT
sesi dashboard (Clerk) Anda; setiap permintaan juga diperiksa terhadap peran
workspace Anda (RBAC). Tidak ada API secret yang berdiri sendiri. Semua endpoint
webhook berada di bawah /workspaces/{workspaceId}.
curl -X POST https://app.tracio.ai/api/v1/workspaces/{workspaceId}/webhooks \ -H "Authorization: Bearer <clerk-session-jwt>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhook/tracio", "events": [] }'Signing secret dihasilkan oleh TRACIO dan dikembalikan sekali saat pembuatan
(dan saat rotasi) di bawah signingSecret. Simpan dengan aman — inilah kunci yang
Anda gunakan untuk memverifikasi tanda tangan.
{ "ok": true, "data": { "id": "wh_abc123", "workspaceEnvironmentId": "b201f2ba-…", "url": "https://your-server.com/webhook/tracio", "events": [], "signingSecret": "f3a9…<hex>", "status": "active", "successRate": 100, "createdAt": "2024-03-12T16:00:00Z" }}Pada pembacaan berikutnya, signingSecret disamarkan (null) — nilai ini hanya
ditampilkan saat pembuatan dan rotasi secret.
| Metode | Path | Deskripsi |
|---|---|---|
GET | /workspaces/{workspaceId}/webhooks | Menampilkan daftar webhook |
PATCH | /workspaces/{workspaceId}/webhooks/{webhookId} | Memperbarui url / events / status |
DELETE | /workspaces/{workspaceId}/webhooks/{webhookId} | Menghapus webhook |
POST | /workspaces/{workspaceId}/webhooks/{webhookId}/test | Mengirim pengiriman uji bertanda tangan |
POST | /workspaces/{workspaceId}/webhooks/{webhookId}/secret/rotate | Merotasi signing secret |
GET | /workspaces/{workspaceId}/webhooks/{webhookId}/deliveries | Menampilkan upaya pengiriman terbaru |
GET | /workspaces/{workspaceId}/webhooks/{webhookId}/deliveries | Menampilkan upaya pengiriman terbaru |
Pengiriman dapat dicoba ulang, jadi handler Anda harus idempoten. Deduplikasi
berdasarkan header X-Tracio-Event-Id (yaitu requestId):
app.post("/webhook/tracio", async (req, res) => { const eventId = req.headers["x-tracio-event-id"] as string
const existing = await db.webhooks.findOne({ eventId }) if (existing) return res.status(200).send("Already processed")
await db.webhooks.insert({ eventId, processedAt: new Date() }) await processWebhookEvent(req.body)
res.status(200).send("OK")})Kembalikan respons 2xx secepat mungkin dan proses payload secara asinkron untuk
menghindari timeout. Respons non-2xx (dan error koneksi) akan dicoba ulang;
kegagalan berulang dapat menonaktifkan webhook secara otomatis.
app.post("/webhook/tracio", async (req, res) => { res.status(200).send("OK") processWebhookEvent(req.body).catch(console.error)})
async function processWebhookEvent(event: WebhookPayload) { await db.events.insert(event)
if (event.decision.riskScore > 50) { await alertFraudTeam(event) }
if (event.bot.result === "bot") { await blockVisitor(event.visitorId) }}Gunakan aksi Test pada sebuah webhook (atau POST .../webhooks/:webhookId/test)
untuk mengirim contoh payload bertanda tangan ke endpoint Anda dan memastikan endpoint
tersebut dapat dijangkau serta memverifikasi tanda tangan dengan benar.
Untuk pengembangan lokal, ekspos server Anda dengan tunnel seperti ngrok:
ngrok http 3000# Use the generated URL as your webhook endpoint