Webhook mengirimkan peristiwa identifikasi ke server Anda secara real-time. Setiap
kali seorang pengunjung teridentifikasi, TRACIO mengirim permintaan HTTP POST ke
URL webhook yang Anda konfigurasikan. Body permintaan itu adalah payload
peristiwanya.
Webhook juga satu-satunya kanal yang mengirimkan verdict terlambat — yaitu verdict ketika perilaku pengunjung membuktikan bahwa ia otomatis setelah halaman sudah dimuat.
Aturlah di dashboard pada Settings → Webhooks. Webhook memerlukan paket Pro atau lebih tinggi.
| Peristiwa | Kapan | Paket |
|---|---|---|
identification | Pada setiap kunjungan — fase primary, late, dan correction | Semua |
account_takeover | Perilaku pada sebuah akun tidak lagi cocok dengan profil pemiliknya | Business ke atas |
account_assessment | Status penilaian pengambilalihan akun untuk sebuah akun: dinilai dan bersih, dinilai dan menyimpang, atau tidak dinilai | Business ke atas |
attack_detected | Lonjakan bot di situs Anda | Business ke atas |
reputation_changed | Reputasi orang yang tertaut berpindah ke band lain | Business ke atas |
transaction_state_changed | Hasil transaksi yang Anda kirim ke POST /v2/transactions berubah | Business ke atas |
login_risk | Penilaian login oleh POST /v2/logins/assess berakhir dengan challenge atau review | Semua |
trusted_device_enrolled | Sebuah perangkat dijadikan tepercaya untuk sebuah akun, atau kepercayaannya diperpanjang | Semua |
trusted_device_revoked | Kepercayaan sebuah perangkat dicabut — melalui API atau dari dashboard | Semua |
affiliate_threshold_crossed | Rekomendasi untuk sebuah afiliasi memburuk (none → review, none → hold, review → hold) | Semua |
Nama peristiwa memakai garis bawah, tidak pernah titik — tidak ada visitor.created
maupun session.created.
Sebuah webhook berlangganan tipe tertentu; nilai terpisah * berarti "setiap tipe,
termasuk yang ditambahkan kemudian". Lima tipe menjadi pengecualian: * tidak
mencakup account_assessment, login_risk, trusted_device_enrolled,
trusted_device_revoked, dan affiliate_threshold_crossed — sebutkan masing-masing
secara eksplisit. account_assessment melaporkan cakupan, bukan temuan, sehingga
datang kira-kira sekali per kunjungan yang membawa linked_id — volumenya berbeda
tingkat dari peristiwa lain. Tipe risiko login dan afiliasi hanya bermakna bagi
integrasi yang memanggil API tersebut. Wildcard yang Anda atur sebelum peristiwa ini
ada bukanlah persetujuan untuk menerimanya.
Tipe yang tidak dikenal ditolak dengan 400 saat langganan dibuat atau diedit,
sehingga salah ketik tidak akan meninggalkan Anda dengan webhook yang diam-diam tidak
pernah terpicu.
identificationSatu kunjungan menghasilkan hingga tiga pengiriman yang berbagi request_id yang
sama:
primary — verdict awal, saat halaman dimuat.late — pengayaan sekitar sembilan detik kemudian, setelah pemeriksaan yang lambat masuk.correction — koreksi berdasarkan perilaku (pointer, ketikan, scrolling).Korelasikan dengan request_id dan bedakan dengan phase. Fase yang lebih akhir
lebih diutamakan: jika primary menyebut human dan correction menyebut bot,
maka yang kedua adalah jawaban yang benar.
Jangan mengandalkan urutan kedatangan. Setiap fase dikirim secara independen dan
dengan jadwal percobaan ulangnya sendiri — jika primary masuk ke percobaan ulang
sementara late berhasil pada percobaan pertama, Anda akan menerimanya dalam urutan
terbalik. Tentukan prioritas dari field phase, bukan dari waktu penerimaan.
Ketiga fase itulah satu-satunya fase peristiwa identification. Satu nilai lain juga
sampai kepada Anda: account_takeover dan account_assessment membawa
phase: "beacon", karena keduanya dimunculkan dari beacon perilaku sesi.
Perhatikan ketidakcocokan yang muncul karenanya, sebab hal ini memengaruhi
idempotensi. Pengiriman identification di produksi memiliki event_id yang persis
berbentuk <request_id>:<phase>, tetapi pengiriman lain melanggar rumus itu. Pada
account_takeover, nilainya <request_id>:ato — akhirannya adalah literal ato,
bukan nilai field phase — dan pada account_assessment nilainya
<request_id>:ato-state-<state>. Pengiriman uji yang dikirim dari dashboard bernilai
<request_id>:test, sementara phase pada body-nya tetap terbaca primary — dan body
skema 1 tidak memiliki field phase sama sekali, sehingga akhiran itu hanya muncul di
header. Gunakan event_id langsung sebagai kunci idempotensi dan jangan pernah
menyusunnya ulang dari request_id dan phase. Cocokkan pada nilai yang Anda tangani
dan abaikan sisanya alih-alih menolak pengirimannya.
Beberapa peristiwa tidak memiliki kunjungan: attack_detected (level workspace),
reputation_changed (level orang), transaction_state_changed (level transaksi),
login_risk, trusted_device_enrolled, dan trusted_device_revoked (level akun),
serta affiliate_threshold_crossed (level afiliasi). Peristiwa itu tidak memiliki
request_id, tidak memiliki visitor_id, dan tidak memiliki blok browser, geo,
bot, maupun decision — kunci-kunci itu memang tidak ada, begitu pula header
X-Tracio-Request-Id. Sebaliknya, account_takeover dan account_assessment
dihasilkan oleh satu kunjungan tertentu dan membawa body identifikasi lengkap sesuai
paket Anda. Jika Anda mem-parsing setiap peristiwa dalam satu handler, periksa event
sebelum menyentuh field kunjungan.
Webhook baru memakai payload versi 3. Webhook yang sudah ada pada versi 1 atau 2 tetap memakai versi yang dipilihnya; kedua skema lama itu dibekukan dan akan dihapus pada 2027-04-12. Untuk bermigrasi, pilih versi 3 pada kartu webhook di dashboard.
| Versi | Status |
|---|---|
1 | Dibekukan; dihapus pada 2027-04-12 |
2 | Dibekukan; dihapus pada 2027-04-12 |
3 | Default untuk webhook baru; kunci snake_case |
Versi 3 membawa field dan nilai yang sama seperti versi 2, dengan kunci snake_case dan
version: 3 di tingkat atas. Algoritma tanda tangan dan header tidak berubah
(X-Tracio-Payload-Version: 3). Kunci di dalam kamus fields milik Anda sendiri
adalah data Anda dan tidak diganti namanya.
| v2 | v3 |
|---|---|
eventId | event_id |
requestId | request_id |
visitorId | visitor_id |
linkedId | linked_id |
userAgent | user_agent |
network.connectionType | network.connection_type |
network.proxyDetected | network.proxy_detected |
network.realIp | network.real_ip |
bot.antidetectScore | bot.antidetect_score |
identification.matchType | identification.match_type |
identification.matchConfidence | identification.match_confidence |
identification.incognitoVisits | identification.incognito_visits |
identification.driftScore | identification.drift_score |
decision.riskScore | decision.risk_score |
decision.suspectScore | decision.suspect_score |
deviceInfo | device_info |
deviceInfo.deviceId | device_info.device_id |
deviceInfo.crossBrowser | device_info.cross_browser |
deviceInfo.linkedBrowsers | device_info.linked_browsers |
screen.colorDepth | screen.color_depth |
screen.pixelRatio | screen.pixel_ratio |
clientHints | client_hints |
clientHints.platformVersion | client_hints.platform_version |
clientHints.deviceName | client_hints.device_name |
osEnvironment | os_environment |
environment.virtualMachine | environment.virtual_machine |
spoofing.spoofedAxes | spoofing.spoofed_axes |
spoofing.anonymousBrowser | spoofing.anonymous_browser |
session.durationSeconds | session.duration_seconds |
velocity.uniqueIps | velocity.unique_ips |
velocity.uniqueLocations | velocity.unique_locations |
accountAlert | account_alert |
accountAlert.accountId | account_alert.account_id |
accountAlert.driftScore | account_alert.drift_score |
accountAssessment | account_assessment |
accountAssessment.accountId | account_assessment.account_id |
accountAssessment.profile.windowsEffective | account_assessment.profile.windows_effective |
accountAssessment.profile.oldestWindowAgeSeconds | account_assessment.profile.oldest_window_age_seconds |
attack.windowMinutes | attack.window_minutes |
attack.recentBots | attack.recent_bots |
attack.recentTotal | attack.recent_total |
personId | person_id |
reputation.overallRisk | reputation.overall_risk |
linkedAccountsCount | linked_accounts_count |
linkedVisitorsCount | linked_visitors_count |
reputationChange | reputation_change |
reputationChange.personId | reputation_change.person_id |
reputationChange.previousBand | reputation_change.previous_band |
reputationChange.visitorIds | reputation_change.visitor_ids |
reputationChange.linkedAccountsCount | reputation_change.linked_accounts_count |
{ "version": 3, "event": "identification", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:primary", // "<request_id>:<phase>" — the idempotency key "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", // visit identifier, shared by all phases "phase": "primary", "visitor_id": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "linked_id": "user-42", // your ?lid=, if you passed one "tag": "checkout", "fields": { "order_ref": "A-1029", "plan": "pro" }, // custom fields that arrived with the visit "timestamp": "2026-07-30T12:00:00Z", "url": "https://shop.example.com/checkout", "ip": "203.0.113.44", "user_agent": "Mozilla/5.0 …", "browser": { "name": "Chrome", "version": "138" }, "os": { "name": "macOS", "version": "15.5" }, "device": "desktop", "gpu": "Intel Iris Plus Graphics 655", // video adapter model, normalized; absent when unknown "geo": { "country": "DE", "city": "Berlin", "lat": 52.52, "lon": 13.405, "timezone": "Europe/Berlin" }, "network": { "vpn": false, "proxy": true, "tor": false, "datacenter": true, "connection_type": "DCH", "proxy_detected": true // HTTP traffic and the raw network path exit through different networks }, "bot": { "result": "human", "score": 3.2 }, // human | bot | uncertain "identification": { "confidence": 0.97, "incognito": false }, "decision": { "action": "real", "risk_score": 12.2 }, // real | fake | suspicious "extensions": ["metamask", "privacy_badger"] // detected browser extensions; absent when none}Nilai nol dan kosong dihilangkan. Field string dan numerik yang bernilai nol
(misalnya bot.type untuk manusia, atau bot.antidetect_score: 0) tidak ada dalam
JSON — jangan menjadikannya wajib di skema Anda, dan bacalah blok bersarang secara
defensif.
bot.score dan decision.risk_score adalah desimal pada skala 0..100 dengan
satu angka di belakang koma — persis angka yang dilaporkan dashboard dan Data API
untuk kunjungan yang sama. (Pada skema v1 yang dibekukan keduanya memakai satuan
berbeda: pecahan 0..1 dan 0..255.)
bot.type berisi nama bot yang dikenali, agen AI terverifikasi (ai_agent:<vendor>),
atau label famili. Lihat Tipe Bot untuk kosakatanya —
nama pemeriksaan internal tidak pernah diekspos, pada paket mana pun.
ai_agent:<vendor> berarti permintaan itu membuktikan bahwa ia berasal dari
infrastruktur vendor tersebut — tanda tangan Web Bot Auth yang sah dengan kunci yang
dipublikasikan vendor, atau alamat di dalam rentang IP yang dipublikasikan vendor —
bukan sekadar mengakuinya di user agent. Permintaan yang mengaku sebagai agen AI dan
dibantah oleh setiap bukti yang dipublikasikan vendor dilaporkan sebagai
impersonation.
| Field | Tipe | Deskripsi |
|---|---|---|
version | number | Versi skema payload (3) |
event | string | Tipe peristiwa |
event_id | string | Pengidentifikasi pengiriman — kunci idempotensi; unik per peristiwa dan dibagikan oleh semua webhook Anda yang menerimanya, stabil di seluruh percobaan ulang |
request_id | string | Pengidentifikasi kunjungan (UUID), dibagikan oleh semua fase kunjungan |
phase | string | primary, late, correction; account_takeover dan account_assessment membawa beacon |
visitor_id | string | Pengidentifikasi pengunjung yang stabil |
linked_id | string | Pengidentifikasi tertaut yang diberikan klien |
tag | string | Tag kustom yang diberikan klien |
fields | object | Field kustom yang dilampirkan ke kunjungan saat pengumpulan. Hanya yang datang bersama kunjungan — field yang dilampirkan kemudian melalui Data API tidak pernah muncul di webhook, pada fase atau percobaan ulang mana pun. Kunci dan nilai berupa string. Tidak ada bila kunjungan tidak membawa field |
timestamp | string | Waktu peristiwa (RFC 3339) |
url | string | URL halaman tempat peristiwa ditangkap |
ip | string | Alamat IP klien |
user_agent | string | String user-agent klien apa adanya |
browser.name / .version | string | Browser yang terdeteksi |
os.name / .version | string | Sistem operasi yang terdeteksi |
device | string | Kelas perangkat (mis. desktop, mobile) |
gpu | string | Model adaptor video seperti yang dilaporkan peramban (WebGL), dinormalisasi menjadi nama yang terbaca (Intel Iris Xe Graphics, Apple M1 Pro); Software renderer untuk perender perangkat lunak; tidak ada bila tidak diketahui |
geo | object | Geolokasi IP: country, city, lat, lon, timezone |
network | object | vpn, proxy, tor, datacenter (boolean) dan connection_type |
network.proxy_detected | boolean | Lalu lintas HTTP kunjungan dan jalur jaringan mentahnya keluar lewat jaringan yang berbeda — ada proxy atau VPN di depan peramban. Dua alamat dari penyedia yang sama (NAT operator, keluaran kedua dari VPN yang sama) tidak dihitung |
bot.result | string | human, bot, atau uncertain |
bot.type | string | Nama bot, agen AI terverifikasi, atau label famili ketika bot terdeteksi |
bot.score | number | Skor bot (0–100) |
identification.confidence | number | Keyakinan identifikasi (0.0–1.0) |
identification.incognito | boolean | Konteks penjelajahan pribadi/incognito |
decision.action | string | real, fake, atau suspicious |
decision.risk_score | number | Skor risiko agregat (0–100) |
extensions | array | Ekstensi browser yang terdeteksi pada kunjungan ini, sebagai slug mesin yang stabil (nama, kategori, dan tautan toko ada di dashboard dan Data API). Tidak ada bila tidak ada yang terdeteksi |
Pro ke atas — bagaimana pengunjung berperilaku dari waktu ke waktu:
{ "identification": { "match_type": "exact", // exact | fuzzy | new — how the visitor was recognized "match_confidence": 0.93, "visits": 42, "incognito_visits": 3 }, // Present when visitor counters are available at event time (usually primary). // A missing block means "no data", not "zeros". "velocity": { "events5m": 7, "unique_ips": 2, "unique_locations": 1 }, "bot": { "antidetect_score": 0 }, // antidetect indicators, 0..100 "session": { "duration_seconds": 95 } // the correction phase and the account_takeover alert}Business ke atas — mengapa verdict-nya seperti itu:
{ "reasons": [ // at most 8, sorted by importance { "code": "headless_browser", "severity": "high" }, { "code": "privacy_hardening", "severity": "low" } ], // Behavioral biometrics — present only when behavioral scoring ran for the // visit. A missing block means "no data", never "nothing suspicious". "behavior": { "score": 87, "verdict": "human", "confidence": 0.92 }, // The smoothed behavior score the account-takeover verdict was made on. It is // measured by beacons that arrive after the identification event, so it is // empty in identification deliveries — see the account_takeover event. "identification": { "drift_score": 0.41 }, "device_info": { "device_id": "…", // the physical device across browsers on it "cross_browser": true, "confidence": 0.88, "linked_browsers": 3 }, "network": { "isp": "Deutsche Telekom", "asn": 3320, // the public address observed on the raw network path, i.e. the address // behind the proxy or VPN; absent when no such address was observed "real_ip": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" } }, "geo": { "isp": "Deutsche Telekom" }, // The desktop environment MEASURED on a Linux machine ("Mint 22+", "Ubuntu", // "GNOME", "KDE"); a User-Agent cannot express a distribution. Absent when // not determined — most Linux visits, and every non-Linux one. "os_environment": "Mint 22+", // What the visit claimed about itself versus what independent checks // measured. Present only when a spoof was actually detected; an empty // `real` field means "the check stayed silent", never "confirmed". With the // `gpu` axis, `claimed.gpu` carries the claimed adapter as the same readable // model name as the top-level `gpu` field. "spoofing": { "detected": true, "claimed": { "os": "Windows 10", "browser": "Chrome 139.0" }, "real": { "os": "macOS" }, "spoofed_axes": ["os", "screen"], // os | gpu | screen | network | browser "anonymous_browser": { "detected": true, "names": ["Linken Sphere"] } }, // Device facts — what the visitor's browser reports about the machine, // sanitized on our side. screen: resolution, color depth and the device // pixel ratio. locale: the browser's own preferred languages and timezone — // as opposed to geo.timezone, which is derived from the IP address; a // mismatch between the two is a common sign of a spoofed location. // client_hints: User-Agent Client Hints — CPU architecture and bitness, // device model (Android: the model code in `model`, e.g. "SM-A556B", and // its marketing name from the Google Play device list in `device_name`, // e.g. "Samsung Galaxy A55 5G") and the exact platform version; only // Chromium-based browsers report them. Every block is absent when the // visit carried no such data, so treat each as optional. "screen": { "width": 2560, "height": 1600, "color_depth": 30, "pixel_ratio": 2 }, "locale": { "languages": ["en-US", "de"], "timezone": "Europe/Berlin" }, "client_hints": { "architecture": "arm", "bitness": "64", "platform_version": "15.5.0" }, // Present only when the video adapter identified itself as a virtual one; // hypervisor is a closed dictionary (vmware, virtualbox, parallels, qemu, // hyperv, bochs, intel-gvt, vgpu). A missing block means no such evidence. "environment": { "virtual_machine": true, "hypervisor": "vmware" }, "decision": { "suspect_score": 4.1 }, "guidance": { "version": 3, "overall": "review" } // see below}Lihat Deteksi Bot untuk kosakata kode
alasan dan arti severity.
Field multi-akun (Business ke atas). Begitu mode person diaktifkan untuk workspace
Anda, body juga membawa person_id, blok reputation (score, categories,
overall_risk), linked_accounts_count, dan linked_visitors_count — apakah
pengunjung tertaut dengan pengunjung dan akun lain. Lapisan person saat ini berjalan
dalam mode observasi, dan sampai lapisan itu aktif, field ini tidak ada di body. Jangan
menjadikannya wajib di skema Anda.
guidance membawa rekomendasi "apa yang harus dilakukan" yang sudah siap pakai untuk
setiap titik integrasi, sehingga Anda tidak perlu menurunkan kebijakan sendiri dari
skor mentah. Blok ini ada di setiap peristiwa v3 Business ke atas — allow dinyatakan
secara eksplisit:
{ "guidance": { "version": 3, "overall": "review", // the strictest advice across the scenarios "payment": "review", // whether to accept the payment "registration": "challenge", // whether to create the account "login": "challenge", // whether to let them into the account "affiliate": "review", // whether to credit the conversion to the partner "basis": ["risk", "network"] // the axes that determined the advice }}Setiap skenario dimulai pada allow dan hanya bergerak naik pada tangga:
allow → challenge → review → deny. Di dalam satu skenario, sumbu terketat yang
terpicu yang menang, dan overall adalah yang terketat di antara keempat skenario.
| Saran | Pembayaran | Pendaftaran | Login | Afiliasi |
|---|---|---|---|---|
allow | Proses saja | Buat saja | Izinkan masuk | Kreditkan konversinya |
challenge | 3-D Secure / konfirmasi | Captcha, konfirmasi email atau telepon | 2FA step-up, autentikasi ulang | Tandai meragukan sampai ada aktivitas nyata |
review | Proses, tetapi antrekan untuk ditinjau | Buat dengan pembatasan | Izinkan masuk, tetapi picu alert | Tahan pembayaran sampai ditinjau |
deny | Jangan proses transaksinya | Tolak pembuatan akun | Jangan izinkan masuk | Jangan kreditkan konversinya |
basis mencantumkan sumbu yang menentukan saran — sumbu yang usulannya lebih ringan
daripada yang diadopsi tidak masuk daftar. Bacalah per kode, alih-alih mengandalkan
kumpulan yang tetap:
Sumbu (basis) | Artinya |
|---|---|
list | Pengunjung cocok dengan salah satu daftar workspace Anda dalam mode enforce. Daftar allow mengesampingkan setiap sumbu: setiap skenario menjadi allow dan basis persis ["list"] |
rule | Kunjungan cocok dengan salah satu aturan workspace Anda dalam mode enforce; aturan hanya mengubah saran skenarionya sendiri (aturan allow hanya melunakkan skenario itu), dan dilewati bila sebuah daftar sudah bertindak |
bot | Detektor bot menyimpulkan ini otomasi — menolak setiap skenario |
risk | Keputusan dan skor risiko keseluruhan kunjungan |
behavior | Biometrik perilaku menyebut bot dengan keyakinan yang cukup — menambah gesekan, bukan penolakan |
account_takeover | Perilaku pada akun ini tidak lagi cocok dengan profil pemiliknya — login ke challenge, pembayaran ke review |
network | Tor, atau VPN yang keluar dari pusat data |
affiliate_pattern | Pola rujukan buatan sendiri — konversi afiliasi ditahan sebagai challenge |
fraud_reputation, multiaccounting | Reputasi dan pengelompokan akun dari orang di balik pengunjung; muncul begitu lapisan person aktif |
version adalah versi kumpulan aturan — angkanya dinaikkan seiring perbaikan logika,
jadi simpanlah di log Anda. Versi 2 memperkenalkan sumbu list, sumbu pertama yang
dapat melunakkan saran; versi 3 memindahkan ambang login account_takeover ke skala
skor verdict pengambilalihan akun, sehingga kunjungan yang sama bisa mendapat deny
di tempat versi 2 memberi challenge, dan sebaliknya.
Guidance bersifat aditif: skenario baru datang sebagai kunci baru tanpa merusak
kontrak. Fase yang lebih akhir menang, kecuali untuk saran parsial: pengiriman
yang dihitung dari kumpulan masukan yang belum lengkap ditandai "partial": true, dan
saran parsial tidak menimpa saran lengkap yang diterima lebih dulu untuk
request_id yang sama. Pada pengiriman biasa, field partial sama sekali tidak ada.
Ambang batas persisnya sengaja tidak didokumentasikan. Saran yang bisa direkayasa
balik menjadi skor berhenti menjadi pertahanan. Ada tepat satu pengecualian yang
disengaja: account_assessment.verdict.threshold, batas keputusan verdict
pengambilalihan akun yang dijelaskan di bawah.
account_takeoverHanya Business dan Enterprise; pada paket yang lebih rendah tidak ada pengiriman yang
dibuat, sekalipun webhook berlangganan peristiwa ini. Body-nya adalah amplop
identifikasi lengkap sesuai paket Anda plus blok account_alert dan blok
account_assessment yang dijelaskan di bagian berikutnya, dikirim paling banyak satu
kali per kunjungan:
{ "version": 3, "event": "account_takeover", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:ato", "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "phase": "beacon", // the alert is raised from a beacon; only the event_id says "ato" "account_alert": { "type": "behavior-drift/2", // kind of alert and the version of its construction "account_id": "user-42", // your linked_id for the account "drift_score": 0.71 // the value the verdict was made on }, "account_assessment": { "type": "behavior-drift/2", "account_id": "user-42", "state": "assessed_alert", "verdict": { "severity": "high", "score": 0.71, "threshold": 0.3565, "scale": "manhattan-normalized", "basis": "ring", "windows": 9 }, "profile": { "windows": 6, "windows_effective": 5, "oldest_window_age_seconds": 1837 } } // …the remaining identification fields}Mengapa type membawa versi. Konstruksi di balik skor ini pernah berubah: skala
dan basis verdict-nya. behavior-drift/2 adalah konstruksi berversi yang pertama.
Jika Anda mengkalibrasi aturan sendiri pada angka ini, anggap perubahan nilai ini
sebagai sinyal untuk memeriksanya kembali.
account_alert.drift_score dan identification.drift_score adalah angka yang
sama (sejak 2026-10-03): skor perilaku yang dihaluskan yang menjadi dasar verdict,
diukur terhadap cincin jendela tepercaya masa lalu milik pemilik akun. Sebelum tanggal
itu identification.drift_score membawa jarak mentah pada skala yang berbeda — jangan
mencampur nilai dari kedua sisi tanggal tersebut. Bila sebuah jendela tidak dinilai,
field-nya tidak ada, bukan nol. Gunakan account_assessment.verdict bila Anda
memerlukan angka itu bersama ambang yang menjadi pembandingnya.
Pada v1 bloknya adalah accountAlert dengan type, linkedId, dan drift, dan tidak
ada blok penilaian. Pada v2 peringatan itu mengganti nama dua field — linkedId →
accountId dan drift → driftScore — dan menambahkan accountAssessment; v3
memakai nama snake_case di atas. Perbarui handler Anda saat Anda mengganti versi, atau
logika pengambilalihan akun Anda akan diam-diam berhenti melihat datanya.
account_assessmentHanya Business dan Enterprise. Memerlukan langganan eksplisit: * tidak
mencakupnya.
Tanpa peristiwa ini, ketiadaan peringatan berarti dua hal berbeda sekaligus: kami menilai sesi terhadap profil pemilik akun dan hasilnya cocok, atau kami sama sekali tidak dapat menilainya — profil pemilik belum siap, jendelanya terlalu jarang, atau lalu lintasnya bukan manusia. Keduanya tampak seperti diam, dan diam terbaca sebagai aman. Biasanya tidak demikian: pada satu hari produksi penuh di bulan September 2026 kami tidak memberi verdict pada 73% jendela dan 80% akun. Proses yang dibangun di atas "tidak ada peringatan berarti aman" dibangun di atas keadaan yang sebagian besar tidak kami miliki.
{ "version": 3, "event": "account_assessment", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:ato-state-not_assessed", "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "phase": "beacon", "account_assessment": { "type": "behavior-drift/2", "account_id": "user-42", "state": "not_assessed", // always present, one of three values "reason": "profile_not_ready", // only when state is not_assessed "profile": { "windows": 3, // windows in the owner's reference ring "windows_effective": 2, // of those, how many yielded a distance "oldest_window_age_seconds": 1837 } } // …the remaining identification fields}state tidak pernah absen dan tidak pernah kosong:
state | Arti | Yang perlu dilakukan |
|---|---|---|
assessed_clear | Dinilai; perilaku cocok dengan pemilik | Tidak ada |
assessed_alert | Dinilai; perilaku menyimpang | Step-up — peringatan account_takeover membawa verdict yang sama |
not_assessed | Tidak ada verdict yang dibuat | Jangan anggap bersih; kembali ke sinyal Anda sendiri |
reason (hanya dengan not_assessed):
reason | Arti |
|---|---|
profile_not_ready | Referensi pemilik saat ini tidak dapat menghasilkan verdict untuk akun ini. Perlakukan sebagai keadaan, bukan tahap: bisa jadi tidak pernah berubah, jadi jangan menunggu |
profile_learning | Referensi masih dikumpulkan |
window_too_sparse | Ada manusia, tetapi jendelanya membawa terlalu sedikit interaksi |
not_computable | Jendela dan referensi tidak memiliki sumbu informatif yang sama |
bot_traffic | Datanya cukup, dan tidak ada manusia untuk dinilai |
Sejak 2026-10-04 18.00 UTC, window_too_sparse dan not_computable tidak lagi
dikeluarkan untuk pemilik yang referensinya sudah siap — jendela semacam itu dilaporkan
sebagai profile_not_ready. Kedua alasan itu tetap ada di daftar karena pengiriman
sebelumnya membawanya: jika handler Anda bercabang berdasarkan keduanya, arahkan
profile_not_ready dengan cara yang sama, dan jangan membandingkan tingkat alasan ini
melintasi tanggal tersebut.
state dan reason tidak dibatasi oleh paket. verdict adalah Business ke atas,
seperti drift_score.
Blok verdict hanya ada dengan assessed_clear dan assessed_alert. Dengan
not_assessed blok ini tidak ada, bukan diisi nol — score 0 di samping threshold 0
akan terbaca sebagai "perilaku cocok persis".
| Field | Arti |
|---|---|
severity | low atau high — dua band, sengaja: band tengah akan memerlukan ambang kedua pada skala yang sama |
score | Nilai yang menjadi dasar verdict |
threshold | Nilai yang benar-benar menjadi pembandingnya |
scale | Cara skor dibangun: manhattan-normalized adalah d/(1+d) dalam ruang perilaku yang dinormalisasi, dengan 0 berarti cocok persis |
basis | ring — jarak ke jendela tepercaya masa lalu pemilik yang terdekat; centroid — jarak ke profil rata-ratanya |
windows | Berapa jendela yang diagregasi ke dalam skor |
score dan threshold selalu berada pada skala yang sama dan bernama, sehingga
pasangan itu mereproduksi keputusan: peringatan terpicu tepat ketika
score > threshold.
Seberapa sering datang. Paling banyak satu pengiriman per keadaan per kunjungan —
dalam praktiknya satu per kunjungan yang membawa linked_id, bukan satu per jendela
perilaku. Keadaan dapat berubah di dalam satu kunjungan — misalnya, referensi selesai
dipelajari dan not_assessed menjadi assessed_clear — dan kemudian Anda menerima
keduanya, secara berurutan. Transisi itu mungkin, bukan dijanjikan: profile_not_ready
bisa bertahan sepanjang umur akun.
Apa yang tidak dijanjikannya. Akun yang paling terlindungi adalah pengguna aktif dengan sesi panjang dan profil referensi lengkap; yang paling kurang terlindungi adalah pengguna yang jarang datang, padahal bagi mereka pengambilalihan lebih berbahaya. Ini adalah sifat konstruksinya, bukan ambang yang dapat disetel. Peristiwa ini melaporkan penyimpangan perilaku dari referensi — bukan klaim untuk menangkap pengambilalihan akun secara umum.
attack_detectedPeringatan lonjakan bot datang dalam amplop v3 yang umum dengan blok attack. Tidak
ada field identifikasi — ini peristiwa level workspace:
{ "version": 3, "event": "attack_detected", "event_id": "c0a8e1f2-…", // alert id; matches X-Tracio-Event-Id "timestamp": "2026-07-30T12:00:00Z", "attack": { "kind": "bot_spike", "severity": "critical", // warning | critical "window_minutes": 15, "recent_bots": 4210, "recent_total": 5100, "expected": 180.5 // the baseline expected over a window this size }}Webhook pada payload versi 1 menerima skema peringatan datar historis — skema itu tidak berubah.
login_risk, trusted_device_enrolled, dan trusted_device_revokedKetiga peristiwa ini berasal dari panggilan Anda sendiri ke API risiko login
(POST /v2/logins/assess) dan perangkat tepercayanya. Peristiwa ini hanya dikirim
pada langganan eksplisit berdasarkan nama — * tidak mencakupnya — dan hanya ke
webhook pada payload versi 2 atau 3; versi 1 tidak memiliki body untuknya.
login_risk dikirim ketika penilaian mengembalikan challenge atau review — tidak
pernah pada allow, deny, atau not_assessed. Blok login_risk-nya adalah
penilaian yang tersimpan, persis seperti yang diberikan respons sinkron. Id
peristiwa: login-risk:<id>, dengan <id> adalah pengidentifikasi kami untuk
catatan yang tersimpan — ambil assessment_id Anda sendiri dari body.trusted_device_enrolled dan trusted_device_revoked dikirim pada setiap
pendaftaran, perpanjangan, dan pencabutan perangkat tepercaya. Blok trusted_device
— user_id, device_ref, enrolled_at, expires_at, revoked_at, revoked_by
(api atau dashboard) — adalah cuplikan pada saat peristiwa terjadi; pada
trusted_device_enrolled, revoked_at dan revoked_by bernilai null. Id
peristiwa: trusted-device:<n>.Peristiwa ini dikirim secara asinkron, hingga sekitar lima detik setelah panggilan, dan
tidak membawa header X-Tracio-Request-Id — gunakan login_risk.request_id untuk
menemukan kunjungannya. Webhook adalah notifikasi untuk audit dan peringatan;
keputusannya sendiri adalah respons sinkron.
{ "version": 3, "event": "login_risk", "event_id": "login-risk:4b1f9e2c-6d3a-4f80-9b7e-2a5c8d1e0f37", "timestamp": "2026-10-17T10:00:00Z", "login_risk": { "assessment_id": "snap-1", "user_id": "alice", "state": "assessed", "recommendation": "challenge", "reasons": ["new_device"] // …the rest of the assessment }}{ "version": 3, "event": "trusted_device_revoked", "event_id": "trusted-device:7", "timestamp": "2026-10-17T11:00:00Z", "trusted_device": { "user_id": "alice", "device_ref": "7978151c54e94838", "enrolled_at": "2026-10-17T10:00:00Z", "expires_at": "2026-11-16T10:00:00Z", "revoked_at": "2026-10-17T11:00:00Z", "revoked_by": "dashboard" }}affiliate_threshold_crossedHanya dikirim pada langganan eksplisit berdasarkan nama — * tidak mencakupnya —
dan hanya ke webhook pada payload versi 2 atau 3. Setiap 15 menit TRACIO menghitung
ulang skor kualitas setiap afiliasi yang datanya cukup dan mengirim peristiwa ketika
rekomendasinya memburuk: none → review, none → hold, atau review → hold.
Perbaikan tidak mengirim apa pun dan memungkinkan pemburukan berikutnya dilaporkan
lagi. Peristiwa ini milik workspace: dikirim ke webhook yang berlangganan di setiap
lingkungan dan tidak membawa header X-Tracio-Request-Id. Id peristiwa:
affiliate-threshold:<affiliate_id>:<recommendation>:<unix time>.
{ "version": 3, "event": "affiliate_threshold_crossed", "event_id": "affiliate-threshold:aff_17:review:1792490400", "timestamp": "2026-10-20T10:00:00Z", "affiliate_threshold": { "affiliate_id": "aff_17", "recommendation": "review", // none | review | hold "previous_recommendation": "none", "quality_score": 72, // 0..100, higher is better "hold_days": 14, // the current hold period of the workspace "crossed_at": "2026-10-20T10:00:00Z", "metrics": { "reg_to_ftd": 0.31, "active14_share": 0.42, "dup_device_share": 0.3, "dup_person_share": 0.1, "ring_share": 0.05, "bonus_hunter_share": 0.2, "dc_vpn_bot_share": 0.1, "chargeback_share": 0.03, "affiliate_fraud_share": 0, "no_visit_share": 0.05 } }}metrics adalah porsi afiliasi selama 90 hari terakhir, sama dengan yang ditampilkan
laporan dashboard: porsi pendaftaran yang mencapai deposit pertama (reg_to_ftd),
porsi pemain yang masih aktif 14 hari setelah pendaftaran (active14_share), serta
porsi konversi dengan perangkat atau orang duplikat, dari jaringan penipuan, dari
pemburu bonus, dari pusat data, VPN, atau bot, yang dilabeli sebagai chargeback atau
penipuan afiliasi, dan tanpa kunjungan yang ditemukan. TRACIO hanya merekomendasikan —
penahanannya sendiri dan setiap keputusan pembayaran tetap berada di platform afiliasi
Anda. Pada payload versi 2 body yang sama memakai kunci camelCase: bloknya
affiliateThreshold, dengan affiliateId, previousRecommendation, qualityScore,
dan seterusnya.
transaction_state_changedBusiness ke atas. Dikirim ketika hasil transaksi yang Anda kirim ke
POST /v2/transactions berubah: aturan memutuskan REVIEW atau DECLINE (APPROVE
tidak mengirim apa pun), seorang analis mengirim transaksi untuk ditinjau di dashboard,
atau label diatur, diganti, atau dihapus. Gunakan untuk menahan bonus selama klaim
sedang ditinjau dan menahan atau melepaskannya begitu hasilnya pasti, tanpa polling.
* mencakup peristiwa ini; peristiwa ini hanya dikirim ke webhook pada payload versi 2
atau 3. Webhook yang terikat ke sebuah lingkungan hanya menerimanya untuk lingkungan
itu.
{ "version": 3, "event": "transaction_state_changed", "event_id": "txn-state:7d1c2b3a-0000-4000-8000-000000000042:118", "timestamp": "2026-10-15T10:04:00Z", "transaction": { "id": "7d1c2b3a-0000-4000-8000-000000000042", "transaction_id": "bonus-claim-1842", "state": "REVIEW", // always the decision of the rules "fraud_score": 50, "effective_state": "DECLINE", // the outcome to act on "recommendations": ["withhold_bonus"], "label": "fraud_detection_bonus_abuse", "label_source": "api", // api | dashboard "label_at": "2026-10-15T10:04:00Z", "previous_effective_state": "REVIEW" // …applied_rules and evidence, as in the transaction response }}| Kunci | Arti |
|---|---|
effective_state | Hasil yang perlu ditindaklanjuti: keadaan dari analis atau label bila ada, jika tidak state |
recommendations | Apa yang harus dilakukan dengan bonus, diturunkan dari effective_state |
label, label_source, label_at | Label saat ini, di mana diatur (api atau dashboard), dan kapan; ketiganya null bila transaksi tidak berlabel |
previous_effective_state | effective_state sebelum perubahan ini; null untuk peristiwa yang dikirim pada keputusan otomatis |
Blok ini menunjukkan transaksi sebagaimana adanya saat peristiwa dikirim: jika dua
perubahan terjadi berdekatan, kedua peristiwa membawa keadaan terbaru, masing-masing
dengan previous_effective_state dan timestamp-nya sendiri. Urutan tidak
dijamin — urutkan berdasarkan timestamp, dan baca transaksi melalui API untuk
keadaan terkininya. Id peristiwa: txn-state:<id>:created untuk keputusan otomatis
dan txn-state:<id>:<n> untuk setiap perubahan berikutnya. Peristiwa ini tidak
menyebutkan siapa yang mengatur label — hanya dari mana asalnya.
reputation_changedBusiness ke atas. Dikirim ketika reputasi lintas pelanggan dari orang yang pernah
dilihat organisasi Anda berpindah ke band lain (misalnya clean → suspicious).
Peristiwa ini memerlukan lapisan person, yang saat ini berjalan dalam mode observasi —
jadi peristiwa itu hanya terpicu untuk workspace yang mengaktifkan resolusi identitas
lintas perangkat. Ini peristiwa level orang: tidak memiliki request_id dan tidak
memiliki blok kunjungan.
{ "version": 3, "event": "reputation_changed", "event_id": "person-reputation:pn_4f2a…:clean->suspicious:2026-07-30T12:10:00.123456Z", "timestamp": "2026-07-30T12:10:00Z", "reputation_change": { "person_id": "pn_4f2a…", "score": 46, "band": "suspicious", // clean | suspicious | fake "previous_band": "clean", "categories": ["chargeback"], "visitor_ids": ["X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"], // the person's visitors in YOUR data "linked_accounts_count": 4 // omitted when zero }}Kunci bloknya adalah reputation_change, bukan reputation: yang terakhir dipakai
untuk reputasi dalam konteks kunjungan tertentu, dan keduanya hal yang berbeda. Skema
1 mengirim bentuk datar historis — field yang sama di tingkat atas.
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 dari "<t>.<rawRequestBody>" dalam bentuk heksadesimal,
dengan kunci berupa secret webhook Anda.Timestamp adalah bagian dari konten yang ditandatangani, dan itulah yang memberi perlindungan terhadap replay.
Ada dua hal yang harus benar, kalau tidak verifikasi akan gagal di produksi:
v1= mana pun. Selama rotasi secret, header
membawa dua tanda tangan, dan parser yang hanya menyimpan salah satunya akan
menolak pengiriman yang sah selama seluruh jendela rotasi.// Express.js exampleimport express from "express"import crypto from "crypto"
const app = express()
// Capture the raw body so the signature can be verified 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 { if (!header) return false
const parts = header.split(",").map((p) => p.trim()) const ts = parts.find((p) => p.startsWith("t="))?.slice(2) if (!ts) return false
// Replay protection: reject timestamps more than five minutes old. if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
// Sign the raw bytes: the "<t>." prefix plus the raw request body. const signed = Buffer.concat([Buffer.from(`${ts}.`, "utf8"), rawBody]) const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex") const exp = Buffer.from(expected, "hex")
// During a rotation window the header carries several v1= — any may match. return parts.some((p) => { if (!p.startsWith("v1=")) return false const got = Buffer.from(p.slice(3), "hex") // Compare lengths BEFORE timingSafeEqual: it throws on differing lengths, // and one junk header would turn the handler into a 500. return got.length === exp.length && crypto.timingSafeEqual(got, exp) })}
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.visitor_id}`) console.log(`Bot: ${event.bot?.result}`) // "human" | "bot" | "uncertain"
res.status(200).send("OK")})Pengiriman skema 2 dan 3 juga membawa X-Tracio-Signature-Ed25519
(t=<unix>,kid=<id>,v1=<base64>). Kedua pihak mengetahui secret HMAC, jadi HMAC
membuktikan bahwa pengirim mengetahui secret tersebut, tetapi bukan bahwa permintaan
itu berasal dari TRACIO; tanda tangan asimetris membuktikannya. Kunci publik
dipublikasikan di https://api.tracio.ai/.well-known/webhook-keys, diindeks
berdasarkan kid: baca field public_key. Alias lama publicKey membawa nilai yang
sama dan akan dihapus pada 2027-04-12.
Pengiriman uji yang dikirim dari dashboard hanya ditandatangani dengan HMAC — kunci
privat platform berada di node pengiriman dan sengaja tidak tersedia bagi dashboard.
Verifikator yang mewajibkan Ed25519 harus meloloskan pengiriman uji (pengiriman
uji membawa akhiran :test pada event_id), jika tidak, pengujian dari dashboard akan
gagal padahal produksi sehat. Kehati-hatian yang sama berlaku untuk pemeriksaan
format: pengiriman uji membawa request_id dalam bentuk test_<hex> dan literal
test_visitor sebagai visitor_id, sehingga handler yang memvalidasi keduanya
terhadap bentuk produksi akan menolak pengiriman yang sebenarnya sudah benar.
Setelah rotasi, kedua secret tetap valid selama 24 jam dan header membawa kedua tanda tangan, sehingga Anda dapat memperbarui konfigurasi tanpa kehilangan pengiriman. Tindakan Revoke now memperpendek jendela tersebut. Perbarui secret di sisi Anda dalam 24 jam: begitu jendela tertutup, secret lama berhenti cocok, dan jika endpoint Anda menjawab tanda tangan yang tidak valid dengan 4xx, lima jawaban seperti itu berturut-turut akan menonaktifkan webhook.
| Header | Deskripsi |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | t=<unix>,v1=<hmac_sha256_hex> — dua v1= selama jendela rotasi |
X-Tracio-Signature-Ed25519 | Tanda tangan platform, t=<unix>,kid=<id>,v1=<base64> (khusus v2 dan v3) |
X-Tracio-Event-Id | Pengidentifikasi pengiriman — kunci idempotensi |
X-Tracio-Request-Id | Pengidentifikasi kunjungan, cocok dengan request_id (khusus v2 dan v3). Dikirim hanya untuk peristiwa kunjungan — identification, account_takeover, dan account_assessment |
X-Tracio-Event-Type | Tipe peristiwa (khusus v2 dan v3) |
X-Tracio-Delivery-Attempt | Nomor percobaan, dimulai dari 1 (khusus v2 dan v3) |
X-Tracio-Payload-Version | 3 atau 2 (khusus v2 dan v3) |
X-Tracio-Webhook-Id | Pengidentifikasi webhook yang menghasilkan pengiriman ini |
Pengiriman dapat dicoba ulang, dan percobaan ulang membawa X-Tracio-Event-Id yang
sama. Lakukan deduplikasi berdasarkan nilai itu:
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")})Perhatikan bahwa event_id unik per peristiwa, bukan per webhook: jika beberapa
webhook di workspace berlangganan peristiwa yang sama, masing-masing menerima
pengiriman dengan pengidentifikasi yang sama. Untuk identification nilainya dibangun
sebagai <request_id>:<phase>, itulah sebabnya ketiga fase dari satu kunjungan
dideduplikasi secara terpisah alih-alih menyatu menjadi satu. Peristiwa tanpa
kunjungan memiliki bentuknya sendiri — lihat masing-masing peristiwa di atas.
Jawablah dengan 2xx — itulah satu-satunya tanda bahwa pengiriman diterima.
| Respons | Apa yang terjadi |
|---|---|
2xx | Pengiriman selesai |
429 Too Many Requests | Tidak dihitung sebagai kegagalan dan tidak menghabiskan percobaan; Retry-After yang lebih panjang dihormati |
408, 425, 5xx, koneksi terputus | Dicoba ulang dengan jeda yang makin panjang |
410 Gone | Endpoint dianggap sudah dihapus — webhook langsung dinonaktifkan |
4xx lainnya | Dicoba ulang, tetapi lima kali berturut-turut menonaktifkan webhook — 400/401/404 tidak sembuh dengan percobaan ulang |
Jadwal percobaan ulang: 5s → 30s → 2min → 10min → 30min → 2h → 6h (8 percobaan). Percobaan pertama masih muat dalam satu menit, jadi restart singkat pada layanan Anda tidak membuat Anda kehilangan notifikasi. Setiap jeda diacak antara separuh dan nilai penuhnya agar percobaan ulang tidak menyerbu serentak setelah gangguan.
Penonaktifan otomatis menuntut ambang batas (20 kegagalan berturut-turut, atau 5 kesalahan konfigurasi) dan setidaknya 15 menit kegagalan berturut-turut — restart singkat tidak dapat mematikan integrasi sekalipun banyak pengiriman menumpuk di antrean. Jeda yang lebih panjang dari 15 menit mengulang hitungannya dari awal. Dashboard menampilkan alasannya, lengkap dengan kode respons dan teks kesalahan, serta tombol Re-enable yang mereset penghitungnya.
| Paket | Webhook per workspace |
|---|---|
| Free | Tidak tersedia |
| Pro | 5 |
| Business | 20 |
| Enterprise | 100 |
Endpoint harus https dengan IP publik — alamat privat dan loopback ditolak,
termasuk pada redirect — dan tidak lebih dari dua tingkat redirect.
Hanya redirect 307 dan 308 yang diikuti. 301, 302, dan 303 memerintahkan
klien beralih ke GET dan membuang body, jadi pengiriman tidak mengikutinya dan
percobaan itu dihitung gagal. Jika load balancer Anda menormalkan URL (menambah www
atau garis miring di akhir), arahkan webhook langsung ke URL finalnya.
Webhook dikelola di dashboard. Dashboard menjalankan API manajemen bercakupan
workspace yang dilayani di host aplikasi (misalnya https://app.tracio.ai/api/v1),
dan endpoint di bawah ini persis itulah yang dipanggilnya. Semua endpoint webhook
berada di bawah /workspaces/{wsId}.
Ini bukan permukaan server-ke-server. API manajemen hanya menerima JWT sesi dashboard Anda, yang diperiksa terhadap peran Anda di workspace (RBAC); secret key
tracio_sk_…ditolak di sini. Karena sesi itu hidup di browser dan berakhir bersamanya, perlakukan panggilan di bawah ini sebagai penjelasan tentang apa yang dilakukan dashboard, bukan sebagai integrasi untuk diotomatiskan. Untuk akses terprogram dari backend Anda sendiri, gunakan Data API yang hanya-baca.
curl -X POST https://app.tracio.ai/api/v1/workspaces/{wsId}/webhooks \ -H "Authorization: Bearer <session-jwt>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhook/tracio", "events": [] }'Secret penandatanganan dihasilkan oleh TRACIO dan dikembalikan satu kali saat
pembuatan (dan saat rotasi) di bawah signingSecret. Simpan dengan aman — itulah
kunci yang Anda gunakan untuk memverifikasi tanda tangan.
{ "ok": true, "data": { "id": "b3d4f8a1-2c67-4e9b-8f05-7a1d3c9e2b48", "workspaceEnvironmentId": "b201f2ba-…", "url": "https://your-server.com/webhook/tracio", "events": [], "signingSecret": "f3a9…<hex>", "status": "active", "successRate": 100, "createdAt": "2026-07-30T12:00:00Z" }}Pada pembacaan berikutnya signingSecret disamarkan (null) — nilainya hanya
diungkap saat pembuatan dan rotasi secret.
| Metode | Path | Deskripsi |
|---|---|---|
GET | /workspaces/{wsId}/webhooks | Daftar webhook |
PATCH | /workspaces/{wsId}/webhooks/{webhookId} | Perbarui url / events / status |
DELETE | /workspaces/{wsId}/webhooks/{webhookId} | Hapus webhook |
POST | /workspaces/{wsId}/webhooks/{webhookId}/test | Kirim pengiriman uji bertanda tangan |
POST | /workspaces/{wsId}/webhooks/{webhookId}/secret/rotate | Rotasi secret penandatanganan |
GET | /workspaces/{wsId}/webhooks/{webhookId}/deliveries | Daftar percobaan pengiriman terbaru |
Kembalikan 2xx secepat mungkin dan proses payload secara asinkron agar tidak
kehabisan waktu:
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?.risk_score > 50) { await alertFraudTeam(event) }
if (event.bot?.result === "bot") { await blockVisitor(event.visitor_id) }}Gunakan tindakan Test pada sebuah webhook (atau
POST .../webhooks/{webhookId}/test) untuk mengirim contoh payload bertanda tangan ke
endpoint Anda dan memastikan endpoint itu 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