Webhook gửi các sự kiện nhận diện đến máy chủ của bạn theo thời gian thực. Mỗi khi một
khách truy cập được nhận diện, TRACIO gửi một yêu cầu HTTP POST tới URL webhook mà
bạn đã cấu hình. Thân yêu cầu chính là payload của sự kiện.
Đây cũng là kênh duy nhất gửi các phán quyết muộn — những phán quyết mà hành vi của khách truy cập chứng minh họ là tự động sau khi trang đã tải xong.
Hãy thiết lập trong dashboard tại Settings → Webhooks. Webhook yêu cầu gói Pro trở lên.
| Sự kiện | Khi nào | Gói |
|---|---|---|
identification | Ở mọi lượt truy cập — các pha primary, late và correction | Tất cả |
account_takeover | Hành vi trong một tài khoản không còn khớp với hồ sơ của chủ tài khoản | Business+ |
account_assessment | Trạng thái đánh giá chiếm đoạt tài khoản của một tài khoản: đã đánh giá và sạch, đã đánh giá và lệch, hoặc chưa đánh giá | Business+ |
attack_detected | Bot tăng vọt trên trang của bạn | Business+ |
reputation_changed | Danh tiếng của một người được liên kết đã chuyển sang dải khác | Business+ |
transaction_state_changed | Kết quả của một giao dịch bạn đã gửi tới POST /v2/transactions đã thay đổi | Business+ |
login_risk | Một lần đánh giá đăng nhập qua POST /v2/logins/assess kết thúc bằng challenge hoặc review | Tất cả |
trusted_device_enrolled | Một thiết bị đã được đặt là tin cậy cho một tài khoản, hoặc độ tin cậy của nó được gia hạn | Tất cả |
trusted_device_revoked | Độ tin cậy của một thiết bị đã bị thu hồi — qua API hoặc từ dashboard | Tất cả |
affiliate_threshold_crossed | Khuyến nghị cho một affiliate trở nên xấu đi (none → review, none → hold, review → hold) | Tất cả |
Tên sự kiện dùng dấu gạch dưới, không bao giờ dùng dấu chấm — không hề có
visitor.created hay session.created.
Một webhook đăng ký các loại cụ thể; giá trị riêng * có nghĩa là "mọi loại, kể cả
những loại được thêm sau này". Có năm loại là ngoại lệ: * không bao gồm
account_assessment, login_risk, trusted_device_enrolled,
trusted_device_revoked và affiliate_threshold_crossed — hãy nêu tên từng loại một
cách tường minh. account_assessment báo cáo độ bao phủ chứ không phải một phát hiện,
nên nó đến khoảng một lần cho mỗi lượt truy cập mang linked_id — khối lượng khác hẳn
về bậc so với các sự kiện còn lại. Các loại rủi ro đăng nhập và affiliate chỉ có ý nghĩa
với một tích hợp gọi những API đó. Một ký tự đại diện bạn đặt trước khi các sự kiện này
tồn tại không phải là sự đồng ý nhận chúng.
Loại không xác định sẽ bị từ chối với mã 400 khi tạo hoặc chỉnh sửa đăng ký, nên một
lỗi gõ nhầm không thể để lại cho bạn một webhook âm thầm không bao giờ kích hoạt.
identificationMột lượt truy cập tạo ra tối đa ba lần gửi cùng chia sẻ một request_id duy nhất:
primary — phán quyết ban đầu, ngay khi trang tải.late — phần làm giàu dữ liệu khoảng chín giây sau, khi các kiểm tra chậm đã hoàn tất.correction — hiệu chỉnh dựa trên hành vi (con trỏ, bàn phím, cuộn trang).Hãy đối chiếu chúng bằng request_id và phân biệt bằng phase. Pha đến sau được ưu
tiên: nếu primary nói human còn correction nói bot thì câu trả lời đúng là
cái thứ hai.
Đừng dựa vào thứ tự đến. Mỗi pha được gửi độc lập và theo lịch thử lại riêng của
nó — nếu primary rơi vào chu trình thử lại trong khi late thành công ngay lần đầu,
bạn sẽ nhận chúng theo thứ tự ngược lại. Hãy xác định mức ưu tiên từ trường phase,
chứ không phải từ thời điểm nhận được.
Đó là ba pha duy nhất của một sự kiện identification. Vẫn còn một giá trị khác đến
được với bạn: account_takeover và account_assessment mang phase: "beacon", bởi cả
hai đều được dấy lên từ beacon hành vi của phiên.
Hãy để ý sự lệch nhau mà điều này tạo ra, vì nó ảnh hưởng tới idempotency. Một lần gửi
identification trên production có event_id đúng bằng <request_id>:<phase>, nhưng
các lần gửi khác phá vỡ công thức đó. Một account_takeover là <request_id>:ato — hậu
tố ở đây là chuỗi ato theo đúng nghĩa đen, chứ không phải giá trị của trường phase —
còn một account_assessment là <request_id>:ato-state-<state>. Một lần gửi thử từ
dashboard là <request_id>:test, trong khi phase trong phần thân của nó vẫn ghi
primary — còn phần thân theo lược đồ 1 thì hoàn toàn không có trường phase, nên
header là nơi duy nhất hậu tố đó xuất hiện. Hãy dùng thẳng event_id làm khóa
idempotency và đừng bao giờ ráp lại nó từ request_id và phase. Hãy so khớp với những
giá trị bạn xử lý và bỏ qua phần còn lại, thay vì từ chối lần gửi đó.
Một số sự kiện không có lượt truy cập: attack_detected (cấp workspace),
reputation_changed (cấp người), transaction_state_changed (cấp giao dịch),
login_risk, trusted_device_enrolled và trusted_device_revoked (cấp tài khoản) và
affiliate_threshold_crossed (cấp affiliate). Chúng không có request_id, không có
visitor_id và không có các khối browser, geo, bot hay decision — những khóa
này đơn giản là không tồn tại, header X-Tracio-Request-Id cũng vậy.
Ngược lại, account_takeover và account_assessment được sinh ra từ một lượt truy cập
cụ thể và mang theo toàn bộ phần thân nhận diện tương ứng với gói của bạn. Nếu bạn phân
tích mọi sự kiện trong cùng một handler, hãy kiểm tra event trước khi động đến các
trường của lượt truy cập.
Webhook mới dùng payload phiên bản 3. Các webhook hiện có ở phiên bản 1 hoặc 2 giữ nguyên phiên bản đã chọn; cả hai lược đồ cũ đều đã đóng băng và sẽ bị gỡ bỏ vào 2027-04-12. Để chuyển đổi, hãy chọn phiên bản 3 trên thẻ webhook trong dashboard.
| Phiên bản | Trạng thái |
|---|---|
1 | Đã đóng băng; bị gỡ bỏ vào 2027-04-12 |
2 | Đã đóng băng; bị gỡ bỏ vào 2027-04-12 |
3 | Mặc định cho webhook mới; khóa snake_case |
Phiên bản 3 mang cùng các trường và giá trị như phiên bản 2, với khóa snake_case và
version: 3 ở cấp cao nhất. Thuật toán chữ ký và các header không đổi
(X-Tracio-Payload-Version: 3). Các khóa bên trong từ điển fields của riêng bạn là dữ
liệu của bạn và không bị đổi tên.
| 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}Giá trị bằng không và rỗng bị lược bỏ. Các trường chuỗi và số có giá trị bằng không
(ví dụ bot.type đối với một con người, hoặc bot.antidetect_score: 0) sẽ không có mặt
trong JSON — đừng bắt buộc chúng trong lược đồ của bạn, và hãy đọc các khối lồng nhau
một cách phòng thủ.
bot.score và decision.risk_score là số thập phân trên thang 0..100 với một chữ
số sau dấu thập phân — đúng những con số mà dashboard và Data API báo cáo cho cùng lượt
truy cập. (Ở lược đồ v1 đã đóng băng, chúng dùng đơn vị khác: lần lượt là phân số 0..1
và 0..255.)
bot.type là tên của một bot đã được nhận diện, một AI agent đã được xác minh
(ai_agent:<vendor>) hoặc nhãn của một họ. Xem Các loại bot
để biết từ vựng — tên các kiểm tra nội bộ không bao giờ được phơi bày, trên bất kỳ gói
nào. ai_agent:<vendor> nghĩa là yêu cầu đã chứng minh nó đến từ hạ tầng của nhà cung
cấp đó — một chữ ký Web Bot Auth hợp lệ được tạo bằng khóa mà nhà cung cấp công bố, hoặc
một địa chỉ nằm trong các dải IP mà nhà cung cấp công bố — chứ không chỉ tự xưng như vậy
trong user agent. Một yêu cầu tự xưng là AI agent nhưng bị mọi bằng chứng nhà cung cấp
công bố bác bỏ sẽ được báo cáo là impersonation.
| Trường | Kiểu | Mô tả |
|---|---|---|
version | number | Phiên bản lược đồ payload (3) |
event | string | Loại sự kiện |
event_id | string | Định danh lần gửi — khóa idempotency; duy nhất cho mỗi sự kiện và dùng chung cho mọi webhook của bạn nhận sự kiện đó, ổn định qua các lần thử lại |
request_id | string | Định danh lượt truy cập (UUID), dùng chung cho mọi pha của lượt đó |
phase | string | primary, late, correction; account_takeover và account_assessment mang beacon |
visitor_id | string | Định danh khách truy cập ổn định |
linked_id | string | Định danh liên kết do máy khách cung cấp |
tag | string | Thẻ tùy chỉnh do máy khách cung cấp |
fields | object | Các trường tùy chỉnh được gắn vào lượt truy cập lúc thu thập. Chỉ những gì đến cùng lượt truy cập — một trường được gắn sau qua Data API không bao giờ xuất hiện trong webhook, ở bất kỳ pha hay lần thử lại nào. Khóa và giá trị là chuỗi. Vắng mặt khi lượt truy cập không mang trường nào |
timestamp | string | Thời điểm sự kiện (RFC 3339) |
url | string | URL trang nơi sự kiện được ghi nhận |
ip | string | Địa chỉ IP của máy khách |
user_agent | string | Chuỗi user-agent thô của máy khách |
browser.name / .version | string | Trình duyệt được nhận diện |
os.name / .version | string | Hệ điều hành được nhận diện |
device | string | Nhóm thiết bị (ví dụ desktop, mobile) |
gpu | string | Model của card đồ họa theo báo cáo của trình duyệt (WebGL), được chuẩn hóa thành tên dễ đọc (Intel Iris Xe Graphics, Apple M1 Pro); Software renderer cho bộ dựng hình bằng phần mềm; vắng mặt khi không xác định được |
geo | object | Định vị theo IP: country, city, lat, lon, timezone |
network | object | vpn, proxy, tor, datacenter (boolean) và connection_type |
network.proxy_detected | boolean | Lưu lượng HTTP của lượt truy cập và các đường mạng thô của nó đi ra qua các mạng khác nhau — có proxy hoặc VPN đứng trước trình duyệt. Hai địa chỉ của cùng một nhà cung cấp (NAT của nhà mạng, một lối ra thứ hai của cùng VPN) thì không tính |
bot.result | string | human, bot hoặc uncertain |
bot.type | string | Tên bot, AI agent đã xác minh hoặc nhãn họ khi phát hiện được bot |
bot.score | number | Điểm bot (0–100) |
identification.confidence | number | Độ tin cậy của việc nhận diện (0.0–1.0) |
identification.incognito | boolean | Ngữ cảnh duyệt riêng tư/ẩn danh |
decision.action | string | real, fake hoặc suspicious |
decision.risk_score | number | Điểm rủi ro tổng hợp (0–100) |
extensions | array | Các tiện ích trình duyệt phát hiện được trong lượt truy cập này, dưới dạng slug máy ổn định (tên, danh mục và liên kết cửa hàng có trong dashboard và Data API). Vắng mặt khi không phát hiện được gì |
Từ Pro trở lên — khách truy cập hành xử ra sao theo thời gian:
{ "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}Từ Business trở lên — vì sao phán quyết lại như vậy:
{ "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}Xem Phát hiện bot để biết từ vựng mã lý do
và ý nghĩa của severity.
Các trường đa tài khoản (từ Business trở lên). Khi chế độ người được bật cho
workspace của bạn, phần thân còn mang person_id, một khối reputation (score,
categories, overall_risk), linked_accounts_count và linked_visitors_count —
khách truy cập có liên kết với những khách truy cập và tài khoản khác hay không. Lớp dữ
liệu về người hiện chạy ở chế độ quan sát, và cho tới khi nó đi vào hoạt động thật, các
trường này vắng mặt trong phần thân. Đừng bắt buộc chúng trong lược đồ của bạn.
guidance mang theo những khuyến nghị "nên làm gì" đã sẵn sàng cho từng điểm tích hợp,
để bạn không phải tự suy ra chính sách từ các điểm số thô. Nó có mặt trong mọi sự kiện
v3 của gói Business+ — allow được ghi rõ ràng:
{ "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 }}Mọi kịch bản đều bắt đầu ở allow và chỉ đi lên trên thang:
allow → challenge → review → deny. Trong một kịch bản, trục nghiêm ngặt nhất
được kích hoạt sẽ thắng, còn overall là mức nghiêm ngặt nhất trong cả bốn kịch bản.
| Khuyến nghị | Thanh toán | Đăng ký | Đăng nhập | Affiliate |
|---|---|---|---|---|
allow | Xử lý bình thường | Cứ tạo tài khoản | Cho vào | Ghi nhận chuyển đổi |
challenge | 3-D Secure / bước xác nhận | Captcha, xác nhận qua email hoặc điện thoại | 2FA nâng cấp, xác thực lại | Đánh dấu là đáng ngờ cho tới khi có hoạt động |
review | Xử lý, nhưng đưa vào hàng đợi rà soát | Tạo nhưng kèm hạn chế | Cho vào, đồng thời bật cảnh báo | Giữ khoản chi trả cho tới khi rà soát xong |
deny | Không xử lý giao dịch | Từ chối tạo tài khoản | Không cho vào | Không ghi nhận chuyển đổi |
basis liệt kê các trục đã quyết định khuyến nghị — một trục có đề xuất nhẹ hơn mức
được chọn sẽ không có trong danh sách. Hãy đọc nó theo mã thay vì giả định một tập cố
định:
Trục (basis) | Ý nghĩa |
|---|---|
list | Khách truy cập khớp với một trong các danh sách của workspace ở chế độ enforce. Danh sách allow ghi đè lên mọi trục: mọi kịch bản trở thành allow và basis đúng bằng ["list"] |
rule | Lượt truy cập khớp với một trong các quy tắc của workspace ở chế độ enforce; một quy tắc chỉ thay đổi khuyến nghị của kịch bản của chính nó (một quy tắc allow chỉ làm mềm kịch bản đó), và bị bỏ qua khi một danh sách đã tác động |
bot | Bộ phát hiện bot kết luận đây là tự động hóa — từ chối mọi kịch bản |
risk | Quyết định và điểm rủi ro tổng thể của lượt truy cập |
behavior | Sinh trắc học hành vi cho rằng đây là bot với độ tin cậy đủ cao — thêm ma sát thay vì từ chối |
account_takeover | Hành vi trong tài khoản này không còn khớp với hồ sơ của chủ tài khoản — đăng nhập chuyển sang challenge, thanh toán sang review |
network | Tor, hoặc VPN đi ra từ một trung tâm dữ liệu |
affiliate_pattern | Dạng giới thiệu tự tạo — chuyển đổi affiliate bị giữ ở mức challenge |
fraud_reputation, multiaccounting | Danh tiếng và việc gom cụm tài khoản của người đứng sau khách truy cập; xuất hiện khi lớp dữ liệu về người đi vào hoạt động |
version là phiên bản của bộ quy tắc — nó được tăng lên khi logic được cải thiện, nên
hãy giữ nó trong log của bạn. Phiên bản 2 đưa vào trục list, trục đầu tiên có thể làm
mềm khuyến nghị; phiên bản 3 chuyển các ngưỡng đăng nhập của account_takeover sang
thang điểm của phán quyết chiếm đoạt tài khoản, nên cùng một lượt truy cập có thể nhận
deny ở chỗ phiên bản 2 cho challenge, và ngược lại.
Guidance mang tính bổ sung: các kịch bản mới đến dưới dạng khóa mới mà không phá vỡ hợp
đồng. Pha đến sau sẽ thắng, ngoại trừ khuyến nghị một phần: lần gửi được tính trên
tập đầu vào chưa đầy đủ sẽ được đánh dấu "partial": true, và khuyến nghị một phần
không ghi đè lên khuyến nghị đầy đủ đã nhận trước đó cho cùng request_id. Trong
một lần gửi thông thường, trường partial hoàn toàn không xuất hiện.
Các ngưỡng chính xác cố ý không được ghi trong tài liệu. Một khuyến nghị có thể bị suy
ngược thành điểm số thì không còn là biện pháp phòng vệ nữa. Có đúng một ngoại lệ có chủ
ý: account_assessment.verdict.threshold, ranh giới quyết định của phán quyết chiếm
đoạt tài khoản được mô tả bên dưới.
account_takeoverChỉ dành cho Business và Enterprise; ở các gói thấp hơn không có lần gửi nào được tạo,
kể cả khi một webhook đã đăng ký sự kiện này. Phần thân là toàn bộ phong bì nhận diện
tương ứng với gói của bạn, cộng thêm khối account_alert và khối account_assessment
được mô tả ở phần tiếp theo, được gửi nhiều nhất một lần cho một lượt truy cập:
{ "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}Vì sao type mang số phiên bản. Cách dựng điểm số đã từng thay đổi: thang và cơ sở
của phán quyết. behavior-drift/2 là cách dựng đầu tiên có đánh phiên bản. Nếu bạn đã
hiệu chỉnh quy tắc của mình theo con số này, hãy coi việc giá trị này thay đổi là tín
hiệu để kiểm tra lại chúng.
account_alert.drift_score và identification.drift_score là cùng một con số
(kể từ 2026-10-03): điểm hành vi đã làm mượt mà phán quyết dựa vào, được đo so với vòng
các cửa sổ tin cậy trước đây của chủ tài khoản. Trước ngày đó
identification.drift_score mang một khoảng cách thô trên một thang khác — đừng trộn
giá trị ở hai phía của ngày này. Khi một cửa sổ không được đánh giá, trường này vắng mặt
chứ không bằng không. Hãy dùng account_assessment.verdict khi bạn cần con số cùng với
ngưỡng mà nó đã được so sánh.
Ở v1, khối này là accountAlert với type, linkedId và drift, và không có khối
đánh giá. Ở v2, cảnh báo đổi tên hai trường — linkedId → accountId và drift →
driftScore — và thêm accountAssessment; v3 dùng các tên snake_case ở trên. Hãy cập
nhật handler khi bạn chuyển phiên bản, nếu không logic chiếm đoạt tài khoản của bạn sẽ
âm thầm ngừng nhìn thấy dữ liệu.
account_assessmentChỉ dành cho Business và Enterprise. Cần đăng ký tường minh: * không bao gồm nó.
Không có sự kiện này, việc không có cảnh báo mang cùng lúc hai nghĩa khác nhau: hoặc chúng tôi đã đánh giá phiên so với hồ sơ của chủ tài khoản và nó khớp, hoặc chúng tôi hoàn toàn không đánh giá được — hồ sơ của chủ tài khoản chưa sẵn sàng, cửa sổ quá thưa, hoặc lưu lượng không phải của một con người. Cả hai trông như im lặng, và im lặng được hiểu là an toàn. Thường thì không phải vậy: trong một ngày production trọn vẹn vào tháng 9 năm 2026, chúng tôi đã không đưa ra phán quyết với 73% cửa sổ và 80% tài khoản. Một quy trình dựa trên "không có cảnh báo nghĩa là ổn" là dựa trên một trạng thái mà phần lớn thời gian chúng tôi không có.
{ "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 không bao giờ vắng mặt và không bao giờ rỗng:
state | Ý nghĩa | Nên làm gì |
|---|---|---|
assessed_clear | Đã đánh giá; hành vi khớp với chủ tài khoản | Không cần làm gì |
assessed_alert | Đã đánh giá; hành vi lệch | Step-up — cảnh báo account_takeover mang cùng phán quyết |
not_assessed | Không có phán quyết | Đừng coi là sạch; quay về các tín hiệu của riêng bạn |
reason (chỉ đi kèm not_assessed):
reason | Ý nghĩa |
|---|---|
profile_not_ready | Tham chiếu của chủ tài khoản hiện không thể cho ra phán quyết cho tài khoản này. Hãy coi đây là một trạng thái, không phải một giai đoạn: nó có thể không bao giờ thay đổi, nên đừng chờ |
profile_learning | Tham chiếu vẫn đang được tích lũy |
window_too_sparse | Có một con người ở đó, nhưng cửa sổ mang quá ít tương tác |
not_computable | Cửa sổ và tham chiếu không có trục thông tin chung nào |
bot_traffic | Có đủ dữ liệu, và không có con người nào để đánh giá |
Kể từ 2026-10-04 18:00 UTC, window_too_sparse và not_computable không còn được đưa
ra cho chủ tài khoản có tham chiếu đã sẵn sàng — cửa sổ như vậy được báo cáo là
profile_not_ready. Hai lý do này vẫn ở trong danh sách vì các lần gửi trước đó mang
chúng: nếu handler của bạn rẽ nhánh theo chúng, hãy định tuyến profile_not_ready theo
cùng cách, và đừng so sánh tỷ lệ của các lý do này ở hai phía của ngày đó.
state và reason không phụ thuộc vào gói. verdict là Business+, giống như
drift_score.
Khối verdict chỉ có mặt cùng assessed_clear và assessed_alert. Với
not_assessed, khối này vắng mặt chứ không được điền bằng số không — score bằng 0
bên cạnh threshold bằng 0 sẽ bị hiểu là "hành vi khớp hoàn toàn".
| Trường | Ý nghĩa |
|---|---|
severity | low hoặc high — cố ý chỉ hai dải: một dải ở giữa sẽ cần ngưỡng thứ hai trên cùng thang |
score | Giá trị mà phán quyết dựa vào |
threshold | Giá trị mà nó thực sự được so sánh |
scale | Cách dựng điểm số: manhattan-normalized là d/(1+d) trong không gian hành vi đã chuẩn hóa, trong đó 0 nghĩa là khớp hoàn toàn |
basis | ring — khoảng cách tới cửa sổ gần nhất trong các cửa sổ tin cậy trước đây của chủ tài khoản; centroid — khoảng cách tới hồ sơ trung bình của họ |
windows | Số cửa sổ được gộp vào điểm số |
score và threshold luôn nằm trên cùng một thang có tên gọi, nên cặp này tái tạo được
quyết định: cảnh báo kích hoạt đúng khi score > threshold.
Tần suất đến. Nhiều nhất một lần gửi cho một trạng thái trong một lượt truy cập —
trên thực tế là một lần cho một lượt truy cập mang linked_id, chứ không phải một lần
cho một cửa sổ hành vi. Trạng thái có thể thay đổi trong cùng một lượt truy cập — ví dụ
tham chiếu học xong và not_assessed trở thành assessed_clear — khi đó bạn nhận được
cả hai, theo thứ tự. Sự chuyển đổi đó là có thể, không phải được hứa hẹn:
profile_not_ready có thể kéo dài suốt vòng đời của tài khoản.
Những gì nó không hứa hẹn. Các tài khoản được bảo vệ tốt nhất là người dùng tích cực với phiên dài và hồ sơ tham chiếu đầy đủ; các tài khoản được bảo vệ kém nhất là người dùng ít khi truy cập, với họ việc bị chiếm đoạt lại nguy hiểm hơn. Đây là đặc tính của cách dựng, không phải một ngưỡng có thể tinh chỉnh. Sự kiện báo cáo độ lệch hành vi so với một tham chiếu — nó không phải lời khẳng định bắt được việc chiếm đoạt tài khoản nói chung.
attack_detectedCảnh báo bot tăng vọt đến trong phong bì v3 chung với một khối attack. Nó không có
trường nhận diện nào — đây là sự kiện ở cấp 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 ở payload phiên bản 1 nhận lược đồ cảnh báo phẳng lịch sử — lược đồ đó không thay đổi.
login_risk, trusted_device_enrolled và trusted_device_revokedBa sự kiện này đến từ chính các lệnh gọi của bạn tới API rủi ro đăng nhập
(POST /v2/logins/assess) và các thiết bị tin cậy của nó. Chúng chỉ được gửi khi có
đăng ký tường minh theo tên — * không bao gồm chúng — và chỉ tới các webhook ở
payload phiên bản 2 hoặc 3; phiên bản 1 không có phần thân cho chúng.
login_risk được gửi khi một lần đánh giá trả về challenge hoặc review — không
bao giờ với allow, deny hay not_assessed. Khối login_risk của nó là bản đánh
giá đã lưu, đúng như phản hồi đồng bộ đã trả. Event id: login-risk:<id>, trong đó
<id> là định danh của bản ghi đã lưu phía chúng tôi — hãy lấy assessment_id của
riêng bạn từ phần thân.trusted_device_enrolled và trusted_device_revoked được gửi ở mọi lần ghi danh,
gia hạn và thu hồi một thiết bị tin cậy. Khối trusted_device của chúng — user_id,
device_ref, enrolled_at, expires_at, revoked_at, revoked_by (api hoặc
dashboard) — là ảnh chụp tại thời điểm xảy ra sự kiện; với
trusted_device_enrolled, revoked_at và revoked_by là null. Event id:
trusted-device:<n>.Chúng được gửi bất đồng bộ, tối đa khoảng năm giây sau lệnh gọi, và không mang header
X-Tracio-Request-Id — hãy dùng login_risk.request_id để tìm lượt truy cập. Webhook
là thông báo phục vụ kiểm toán và cảnh báo; bản thân quyết định là phản hồi đồng bộ.
{ "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_crossedChỉ được gửi khi có đăng ký tường minh theo tên — * không bao gồm nó — và chỉ tới
các webhook ở payload phiên bản 2 hoặc 3. Cứ 15 phút một lần, TRACIO tính lại điểm chất
lượng của từng affiliate có đủ dữ liệu và gửi sự kiện khi khuyến nghị của nó xấu đi:
none → review, none → hold hoặc review → hold. Một sự cải thiện không gửi gì và cho
phép lần xấu đi kế tiếp được báo cáo lại. Sự kiện thuộc về workspace: nó đi tới các
webhook đã đăng ký của mọi môi trường và không mang header X-Tracio-Request-Id. Event
id: 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 là các tỷ lệ của affiliate trong 90 ngày gần nhất, giống như báo cáo trong
dashboard hiển thị: tỷ lệ đăng ký đạt tới lần nạp tiền đầu tiên (reg_to_ftd), tỷ lệ
người chơi vẫn hoạt động 14 ngày sau khi đăng ký (active14_share), và tỷ lệ chuyển đổi
có thiết bị hoặc người trùng lặp, đến từ một vòng gian lận, từ những kẻ săn thưởng, từ
trung tâm dữ liệu, VPN hoặc bot, được gắn nhãn chargeback hoặc gian lận affiliate, và
không tìm thấy lượt truy cập. TRACIO chỉ khuyến nghị — bản thân việc giữ lại và mọi
quyết định chi trả vẫn nằm trong nền tảng affiliate của bạn. Ở payload phiên bản 2,
cùng phần thân đó có khóa camelCase: khối là affiliateThreshold, với affiliateId,
previousRecommendation, qualityScore và các khóa tương tự.
transaction_state_changedTừ Business trở lên. Được gửi khi kết quả của một giao dịch bạn đã gửi tới
POST /v2/transactions thay đổi: các quy tắc quyết định REVIEW hoặc DECLINE (một
APPROVE không gửi gì), một chuyên viên phân tích đã đưa giao dịch đi rà soát trong
dashboard, hoặc một nhãn đã được đặt, thay thế hay gỡ bỏ. Hãy dùng nó để giữ một khoản
thưởng trong khi yêu cầu đang được rà soát và giữ lại hoặc giải phóng nó khi kết quả đã
ngã ngũ, mà không cần thăm dò. * bao gồm sự kiện này; nó chỉ được gửi tới các webhook
ở payload phiên bản 2 hoặc 3. Một webhook gắn với một môi trường chỉ nhận nó cho môi
trường đó.
{ "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 }}| Khóa | Ý nghĩa |
|---|---|
effective_state | Kết quả cần hành động theo: trạng thái của chuyên viên phân tích hoặc của nhãn khi có, nếu không thì là state |
recommendations | Việc cần làm với khoản thưởng, suy ra từ effective_state |
label, label_source, label_at | Nhãn hiện tại, nơi nó được đặt (api hoặc dashboard) và thời điểm; cả ba là null khi giao dịch không có nhãn |
previous_effective_state | effective_state trước thay đổi này; null với sự kiện được gửi cho quyết định tự động |
Khối này cho thấy giao dịch như nó đang là tại thời điểm sự kiện được gửi: nếu hai
thay đổi nối tiếp nhau nhanh, cả hai sự kiện đều mang trạng thái mới nhất, mỗi sự kiện
với previous_effective_state và timestamp riêng. Thứ tự không được bảo đảm — hãy
sắp xếp theo timestamp, và đọc giao dịch qua API để có trạng thái hiện tại. Event id:
txn-state:<id>:created cho quyết định tự động và txn-state:<id>:<n> cho mọi thay
đổi sau đó. Sự kiện không cho biết ai đã đặt nhãn — chỉ cho biết nhãn đến từ đâu.
reputation_changedTừ Business trở lên. Được gửi khi danh tiếng xuyên khách hàng của một người mà tổ chức
của bạn đã thấy chuyển sang dải khác (ví dụ clean → suspicious). Nó cần đến lớp dữ
liệu về người, hiện đang chạy ở chế độ quan sát — nên sự kiện chỉ kích hoạt với những
workspace đã bật phân giải danh tính xuyên thiết bị. Đây là sự kiện ở cấp người: nó
không có request_id và không có các khối của lượt truy cập.
{ "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 }}Khóa của khối là reputation_change, không phải reputation: khóa sau đã được dùng cho
danh tiếng trong ngữ cảnh của một lượt truy cập cụ thể, và hai thứ này khác nhau. Lược
đồ 1 gửi dạng phẳng lịch sử — cùng các trường ở cấp cao nhất.
Mỗi lần gửi đều kèm header X-Tracio-Signature:
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed…t là dấu thời gian Unix (tính bằng giây) tại thời điểm yêu cầu được ký.v1 là HMAC-SHA256 của "<t>.<rawRequestBody>" được mã hóa hex, với khóa là secret
webhook của bạn.Dấu thời gian là một phần của nội dung được ký, nhờ đó có được khả năng chống replay.
Có hai điều phải làm đúng, nếu không việc xác minh sẽ thất bại trên môi trường production:
v1= nào. Trong thời gian xoay vòng
secret, header mang hai chữ ký, và một bộ phân tích chỉ giữ lại một trong số đó sẽ
từ chối những lần gửi hợp lệ suốt cả cửa sổ xoay vòng.// 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")})Các lần gửi theo lược đồ 2 và 3 còn mang thêm X-Tracio-Signature-Ed25519
(t=<unix>,kid=<id>,v1=<base64>). Cả hai phía đều biết secret HMAC, nên HMAC chứng
minh rằng người gửi biết secret chứ không chứng minh rằng yêu cầu bắt nguồn từ TRACIO;
chữ ký bất đối xứng làm được điều đó. Khóa công khai được công bố tại
https://api.tracio.ai/.well-known/webhook-keys, tra theo kid: hãy đọc trường
public_key. Bí danh cũ publicKey mang cùng giá trị và sẽ bị gỡ bỏ vào
2027-04-12.
Các lần gửi thử từ dashboard chỉ được ký bằng HMAC — khóa riêng của nền tảng nằm trên
các node gửi và cố ý không được cấp cho dashboard. Bộ xác minh bắt buộc phải có
Ed25519 thì phải cho các lần gửi thử đi qua (chúng mang hậu tố :test trên
event_id), nếu không việc thử từ dashboard sẽ thất bại trong khi production vẫn khỏe
mạnh. Cũng phải thận trọng tương tự với các kiểm tra định dạng: một lần gửi thử mang
request_id ở dạng test_<hex> và visitor_id là chuỗi test_visitor theo đúng nghĩa
đen, nên một handler đối chiếu chúng với khuôn dạng của production sẽ từ chối một lần
gửi vốn hoàn toàn hợp lệ.
Sau khi xoay vòng, cả hai secret vẫn hợp lệ trong 24 giờ và header mang cả hai chữ ký, nhờ đó bạn có thể cập nhật cấu hình mà không mất lần gửi nào. Thao tác Revoke now rút ngắn cửa sổ này. Hãy cập nhật secret ở phía bạn trong vòng 24 giờ: khi cửa sổ khép lại, secret cũ ngừng khớp, và nếu endpoint của bạn trả về 4xx cho chữ ký không hợp lệ thì năm phản hồi như vậy liên tiếp sẽ vô hiệu hóa webhook.
| Header | Mô tả |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | t=<unix>,v1=<hmac_sha256_hex> — hai v1= trong cửa sổ xoay vòng |
X-Tracio-Signature-Ed25519 | Chữ ký nền tảng, t=<unix>,kid=<id>,v1=<base64> (chỉ v2 và v3) |
X-Tracio-Event-Id | Định danh lần gửi — khóa idempotency |
X-Tracio-Request-Id | Định danh lượt truy cập, khớp với request_id (chỉ v2 và v3). Chỉ được gửi với sự kiện của lượt truy cập — identification, account_takeover và account_assessment |
X-Tracio-Event-Type | Loại sự kiện (chỉ v2 và v3) |
X-Tracio-Delivery-Attempt | Số thứ tự lần thử, bắt đầu từ 1 (chỉ v2 và v3) |
X-Tracio-Payload-Version | 3 hoặc 2 (chỉ v2 và v3) |
X-Tracio-Webhook-Id | Định danh của webhook đã tạo ra lần gửi này |
Các lần gửi có thể được thử lại, và lần thử lại mang cùng X-Tracio-Event-Id. Hãy khử
trùng lặp dựa trên giá trị đó:
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")})Lưu ý rằng event_id là duy nhất cho mỗi sự kiện, chứ không phải cho mỗi webhook:
nếu nhiều webhook trong workspace cùng đăng ký một sự kiện, mỗi webhook sẽ nhận được
một lần gửi với cùng định danh đó. Với identification, nó được dựng theo dạng
<request_id>:<phase>, nhờ vậy ba pha của cùng một lượt truy cập được khử trùng lặp độc
lập với nhau chứ không gộp lại thành một. Các sự kiện không có lượt truy cập có dạng
riêng của chúng — xem từng sự kiện ở trên.
Hãy trả về 2xx — đó là dấu hiệu duy nhất cho thấy lần gửi đã được chấp nhận.
| Phản hồi | Điều gì xảy ra |
|---|---|
2xx | Gửi hoàn tất |
429 Too Many Requests | Không tính là thất bại và không tiêu tốn một lần thử; Retry-After dài hơn sẽ được tôn trọng |
408, 425, 5xx, mất kết nối | Thử lại với khoảng nghỉ tăng dần |
410 Gone | Endpoint được coi là đã bị gỡ bỏ — webhook bị vô hiệu hóa ngay lập tức |
Các mã 4xx khác | Vẫn thử lại, nhưng năm lần liên tiếp sẽ vô hiệu hóa webhook — 400/401/404 không thể chữa bằng cách thử lại |
Lịch thử lại: 5s → 30s → 2min → 10min → 30min → 2h → 6h (8 lần thử). Những lần thử lại đầu tiên nằm gọn trong một phút, nên việc khởi động lại dịch vụ trong chốc lát không khiến bạn mất thông báo. Mỗi khoảng nghỉ được ngẫu nhiên hóa trong khoảng từ một nửa đến trọn giá trị, để các lần thử lại không đồng loạt dội về sau một sự cố.
Tự động vô hiệu hóa đòi hỏi đồng thời một ngưỡng (20 lần thất bại liên tiếp, hoặc 5 lỗi cấu hình) và ít nhất 15 phút thất bại liên tục — một lần khởi động lại ngắn không thể giết chết tích hợp, kể cả khi nhiều lần gửi đã dồn trong hàng đợi. Khoảng trống dài hơn 15 phút sẽ khởi động lại bộ đếm. Dashboard hiển thị lý do kèm mã phản hồi và nội dung lỗi, cùng nút Re-enable để đặt lại các bộ đếm.
| Gói | Số webhook trên mỗi workspace |
|---|---|
| Free | Không khả dụng |
| Pro | 5 |
| Business | 20 |
| Enterprise | 100 |
Endpoint phải là https với IP công khai — địa chỉ riêng và loopback đều bị từ chối,
kể cả khi xuất hiện sau một lần chuyển hướng — và không sâu quá hai lần chuyển hướng.
Chỉ chuyển hướng 307 và 308 được đi theo. 301, 302 và 303 yêu cầu máy
khách chuyển sang GET và bỏ phần thân, nên lần gửi sẽ không đi theo chúng và lần thử đó
bị tính là thất bại. Nếu bộ cân bằng tải của bạn chuẩn hóa URL (thêm www hoặc dấu gạch
chéo ở cuối), hãy trỏ webhook thẳng tới URL cuối cùng.
Webhook được quản lý trong dashboard. Dashboard vận hành một API quản trị trong
phạm vi workspace, được phục vụ trên host ứng dụng (ví dụ
https://app.tracio.ai/api/v1), và các endpoint bên dưới chính là những gì nó gọi.
Toàn bộ endpoint webhook nằm dưới /workspaces/{wsId}.
Đây không phải bề mặt dành cho giao tiếp giữa các máy chủ. API quản trị chỉ chấp nhận JWT phiên dashboard của bạn, được đối chiếu với vai trò của bạn trong workspace (RBAC); khóa bí mật dạng
tracio_sk_…bị từ chối ở đây. Vì phiên đó sống trong trình duyệt và hết hạn cùng với nó, hãy xem các lệnh gọi bên dưới như một mô tả về những gì dashboard làm, chứ không phải một tích hợp để tự động hóa. Để truy cập bằng chương trình từ backend của riêng bạn, hãy dùng Data API chỉ đọc.
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 ký do TRACIO sinh ra và chỉ được trả về một lần khi tạo (và khi xoay vòng)
trong trường signingSecret. Hãy lưu trữ an toàn — đó là khóa bạn dùng để xác minh chữ
ký.
{ "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" }}Ở những lần đọc sau, signingSecret bị che (null) — nó chỉ được tiết lộ khi tạo và
khi xoay vòng secret.
| Phương thức | Đường dẫn | Mô tả |
|---|---|---|
GET | /workspaces/{wsId}/webhooks | Liệt kê webhook |
PATCH | /workspaces/{wsId}/webhooks/{webhookId} | Cập nhật url / events / status |
DELETE | /workspaces/{wsId}/webhooks/{webhookId} | Xóa một webhook |
POST | /workspaces/{wsId}/webhooks/{webhookId}/test | Gửi một lần gửi thử có chữ ký |
POST | /workspaces/{wsId}/webhooks/{webhookId}/secret/rotate | Xoay vòng secret ký |
GET | /workspaces/{wsId}/webhooks/{webhookId}/deliveries | Liệt kê các lần gửi gần đây |
Hãy trả về 2xx sớm nhất có thể và xử lý payload bất đồng bộ để tránh hết thời gian
chờ:
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) }}Dùng thao tác Test trên một webhook (hoặc POST .../webhooks/{webhookId}/test) để
gửi một payload mẫu có chữ ký tới endpoint của bạn và xác nhận rằng nó có thể truy cập
được và đang xác minh chữ ký đúng cách.
Khi phát triển cục bộ, hãy phơi máy chủ của bạn qua một tunnel như ngrok:
ngrok http 3000# Use the generated URL as your webhook endpoint