Webhook ส่งเหตุการณ์การระบุตัวตนไปยังเซิร์ฟเวอร์ของคุณแบบเรียลไทม์ ทุกครั้งที่มีการ
ระบุตัวตนผู้เข้าชม TRACIO จะส่งคำขอ HTTP POST ไปยัง URL ของ webhook ที่คุณกำหนดไว้
ตัวเนื้อหาของคำขอคือเพย์โหลดของเหตุการณ์นั้นเอง
นอกจากนี้ webhook ยังเป็นช่องทางเดียวที่ส่งคำตัดสินที่มาภายหลัง — คือกรณีที่ พฤติกรรมของผู้เข้าชมพิสูจน์ว่าเป็นระบบอัตโนมัติหลังจากหน้าเว็บโหลดเสร็จไปแล้ว
ตั้งค่าได้ในแดชบอร์ดที่ Settings → Webhooks โดย webhook ต้องใช้แพ็กเกจ Pro ขึ้นไป
| เหตุการณ์ | เมื่อใด | แพ็กเกจ |
|---|---|---|
identification | ทุกการเข้าชม — เฟส primary, late และ correction | ทั้งหมด |
account_takeover | พฤติกรรมภายใต้บัญชีหนึ่งไม่ตรงกับโปรไฟล์ของเจ้าของอีกต่อไป | Business ขึ้นไป |
account_assessment | สถานะการประเมินการยึดบัญชีของบัญชีหนึ่ง: ประเมินแล้วและปกติ ประเมินแล้วและเบี่ยงเบน หรือไม่ได้ประเมิน | Business ขึ้นไป |
attack_detected | บอตพุ่งสูงผิดปกติบนเว็บไซต์ของคุณ | Business ขึ้นไป |
reputation_changed | ชื่อเสียงของบุคคลที่เชื่อมโยงเลื่อนไปอยู่อีกระดับหนึ่ง | Business ขึ้นไป |
transaction_state_changed | ผลลัพธ์ของธุรกรรมที่คุณส่งไปยัง POST /v2/transactions เปลี่ยนไป | Business ขึ้นไป |
login_risk | การประเมินการเข้าสู่ระบบโดย POST /v2/logins/assess จบลงด้วย challenge หรือ review | ทั้งหมด |
trusted_device_enrolled | อุปกรณ์ถูกทำให้เป็นอุปกรณ์ที่เชื่อถือได้ของบัญชี หรือความเชื่อถือของอุปกรณ์ถูกต่ออายุ | ทั้งหมด |
trusted_device_revoked | ความเชื่อถือของอุปกรณ์ถูกเพิกถอน — ผ่าน API หรือจากแดชบอร์ด | ทั้งหมด |
affiliate_threshold_crossed | คำแนะนำสำหรับพันธมิตรแย่ลง (none → review, none → hold, review → hold) | ทั้งหมด |
ชื่อเหตุการณ์ใช้ขีดล่าง ไม่เคยใช้จุด — ไม่มี visitor.created หรือ session.created
webhook หนึ่งตัวจะสมัครรับเฉพาะประเภทที่ระบุ ส่วนค่าพิเศษ * หมายถึง "ทุกประเภท
รวมถึงประเภทที่จะเพิ่มในภายหลัง" มีห้าประเภทที่เป็นข้อยกเว้น: * ไม่รวม
account_assessment, login_risk, trusted_device_enrolled,
trusted_device_revoked และ affiliate_threshold_crossed — ต้องระบุชื่อของแต่ละประเภท
อย่างชัดเจน account_assessment รายงานความครอบคลุมของการประเมิน ไม่ใช่สิ่งที่ตรวจพบ
จึงมาถึงราวหนึ่งครั้งต่อการเข้าชมที่มี linked_id — ปริมาณคนละระดับกับเหตุการณ์อื่น
ประเภทความเสี่ยงการเข้าสู่ระบบและพันธมิตรมีความหมายเฉพาะกับการผสานรวมที่เรียก API
เหล่านั้น wildcard ที่คุณตั้งไว้ก่อนเหตุการณ์เหล่านี้จะมีขึ้นไม่ถือเป็นความยินยอมที่จะรับมัน
ประเภทที่ไม่รู้จักจะถูกปฏิเสธด้วยรหัส 400 ตอนสร้างหรือแก้ไขการสมัครรับ การพิมพ์ผิด
จึงไม่ทำให้คุณเหลือ webhook ที่เงียบหายและไม่เคยทำงาน
identificationการเข้าชมหนึ่งครั้งทำให้เกิดการส่งได้สูงสุดสามครั้ง ซึ่งใช้ request_id เดียวกัน
primary — คำตัดสินแรกตอนหน้าเว็บโหลดlate — การเสริมข้อมูลราวเก้าวินาทีถัดมา เมื่อการตรวจสอบที่ช้าเสร็จสิ้นcorrection — การแก้ไขตามพฤติกรรม (ตัวชี้ แป้นพิมพ์ การเลื่อนหน้า)ให้จับคู่ด้วย request_id และแยกแยะด้วย phase เฟสที่มาทีหลังมีน้ำหนักเหนือกว่า
หาก primary บอกว่า human และ correction บอกว่า bot คำตอบที่ถูกต้องคืออย่างหลัง
อย่ายึดลำดับการมาถึง แต่ละเฟสถูกส่งอย่างเป็นอิสระและมีตารางการลองใหม่ของตัวเอง —
ถ้า primary เข้าสู่การลองใหม่ในขณะที่ late สำเร็จตั้งแต่ครั้งแรก คุณจะได้รับมันใน
ลำดับกลับกัน ให้ตัดสินลำดับความสำคัญจากฟิลด์ phase ไม่ใช่จากเวลาที่ได้รับ
สามเฟสนั้นเป็นเฟสเดียวที่เหตุการณ์ identification มี แต่ยังมีอีกหนึ่งค่าที่ส่งมาถึงคุณ
คือ account_takeover และ account_assessment มี phase: "beacon" เพราะทั้งสอง
ถูกจุดขึ้นจากบีคอนเชิงพฤติกรรมของเซสชัน
โปรดสังเกตความไม่สอดคล้องที่เกิดขึ้นตรงนี้ เพราะมันกระทบกับ idempotency การส่ง
identification บนระบบจริงจะมี event_id เท่ากับ <request_id>:<phase> พอดี แต่การส่ง
อื่นแหกสูตรนี้ account_takeover จะเป็น <request_id>:ato — ส่วนต่อท้ายคือสตริงตรงตัว
ว่า ato ไม่ใช่ค่าของฟิลด์ phase — และ account_assessment จะเป็น
<request_id>:ato-state-<state> ส่วนการส่งทดสอบจากแดชบอร์ดจะเป็น <request_id>:test
ขณะที่ phase ในเนื้อหายังคงอ่านได้ว่า primary — และเนื้อหาแบบสคีมา 1 ไม่มีฟิลด์
phase เลย ส่วนต่อท้ายนั้นจึงปรากฏอยู่ในเฮดเดอร์เพียงที่เดียว ให้ใช้ event_id เป็นคีย์
idempotency โดยตรง และอย่าประกอบมันขึ้นใหม่จาก request_id กับ phase เด็ดขาด
ให้จับคู่เฉพาะค่าที่คุณรองรับและเพิกเฉยต่อค่าอื่นแทนที่จะปฏิเสธการส่งนั้นทิ้ง
บางเหตุการณ์ไม่มีการเข้าชม: attack_detected (ระดับ workspace), reputation_changed
(ระดับบุคคล), transaction_state_changed (ระดับธุรกรรม), login_risk,
trusted_device_enrolled และ trusted_device_revoked (ระดับบัญชี) และ
affiliate_threshold_crossed (ระดับพันธมิตร) เหตุการณ์เหล่านี้ไม่มี request_id ไม่มี
visitor_id และไม่มีบล็อก browser, geo, bot หรือ decision — คีย์เหล่านี้ไม่ปรากฏ
เลย เช่นเดียวกับเฮดเดอร์ X-Tracio-Request-Id ในทางกลับกัน account_takeover และ
account_assessment เกิดจากการเข้าชมหนึ่งครั้งโดยเฉพาะ และมาพร้อมเนื้อหาการระบุตัวตน
ครบชุดตามแพ็กเกจของคุณ หากคุณแยกวิเคราะห์ทุกเหตุการณ์ใน handler เดียว ให้ตรวจ event
ก่อนแตะฟิลด์ที่เกี่ยวกับการเข้าชม
webhook ใหม่ใช้เพย์โหลดเวอร์ชัน 3 ส่วน webhook ที่มีอยู่บนเวอร์ชัน 1 หรือ 2 จะคงเวอร์ชัน ที่เลือกไว้ สคีมาเก่าทั้งสองถูกตรึงไว้และจะถูกนำออกในวันที่ 2027-04-12 หากต้องการ ย้าย ให้เลือกเวอร์ชัน 3 บนการ์ด webhook ในแดชบอร์ด
| เวอร์ชัน | สถานะ |
|---|---|
1 | ถูกตรึงไว้ จะถูกนำออกในวันที่ 2027-04-12 |
2 | ถูกตรึงไว้ จะถูกนำออกในวันที่ 2027-04-12 |
3 | ค่าเริ่มต้นสำหรับ webhook ใหม่ คีย์แบบ snake_case |
เวอร์ชัน 3 มีฟิลด์และค่าเดียวกับเวอร์ชัน 2 โดยใช้คีย์แบบ snake_case และมี version: 3
ที่ระดับบนสุด อัลกอริทึมของลายเซ็นและเฮดเดอร์ไม่เปลี่ยนแปลง
(X-Tracio-Payload-Version: 3) คีย์ภายในพจนานุกรม fields ของคุณเองเป็นข้อมูลของคุณ
และจะไม่ถูกเปลี่ยนชื่อ
| 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", // รุ่นของการ์ดแสดงผล ผ่านการทำให้เป็นมาตรฐานแล้ว จะไม่มีเมื่อไม่ทราบ "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 กับเส้นทางเครือข่ายดิบออกผ่านคนละเครือข่ายกัน }, "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}ค่าศูนย์และค่าว่างจะถูกตัดออก ฟิลด์สตริงและตัวเลขที่มีค่าเป็นศูนย์ (เช่น bot.type
สำหรับมนุษย์ หรือ bot.antidetect_score: 0) จะไม่ปรากฏใน JSON — อย่ากำหนดให้เป็น
ฟิลด์บังคับในสคีมาของคุณ และให้อ่านบล็อกที่ซ้อนกันอย่างระมัดระวัง
bot.score และ decision.risk_score เป็นทศนิยมในสเกล 0..100 ที่มีหนึ่งตำแหน่ง
หลังจุดทศนิยม — ตรงกับตัวเลขที่แดชบอร์ดและ Data API รายงานสำหรับการเข้าชมเดียวกัน
(ในสคีมา v1 ที่ถูกตรึงไว้ ทั้งสองใช้หน่วยต่างออกไป คือเศษส่วน 0..1 และ 0..255
ตามลำดับ)
bot.type เป็นได้ทั้งชื่อของบอตที่ระบบรู้จัก เอเจนต์ AI ที่ยืนยันแล้ว (ai_agent:<vendor>)
หรือป้ายกำกับของกลุ่ม ดูชุดคำศัพท์ได้ที่ ประเภทของบอต
— ชื่อการตรวจสอบภายในจะไม่ถูกเปิดเผยไม่ว่าจะเป็นแพ็กเกจใดก็ตาม ai_agent:<vendor>
หมายความว่าคำขอพิสูจน์ได้ว่ามาจากโครงสร้างพื้นฐานของผู้ให้บริการรายนั้นจริง — ด้วยลายเซ็น
Web Bot Auth ที่ถูกต้องซึ่งสร้างด้วยคีย์ที่ผู้ให้บริการเผยแพร่ หรือด้วยที่อยู่ภายในช่วง IP
ที่ผู้ให้บริการเผยแพร่ — ไม่ใช่เพียงแค่อ้างไว้ใน user agent คำขอที่อ้างว่าเป็นเอเจนต์ AI
แต่ขัดแย้งกับหลักฐานทุกอย่างที่ผู้ให้บริการเผยแพร่ จะถูกรายงานเป็น impersonation
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
version | number | เวอร์ชันสคีมาของเพย์โหลด (3) |
event | string | ประเภทของเหตุการณ์ |
event_id | string | ตัวระบุการส่ง — คีย์สำหรับ idempotency ไม่ซ้ำกันต่อเหตุการณ์และใช้ร่วมกันในทุก webhook ของคุณที่ได้รับเหตุการณ์นั้น คงที่ตลอดการลองใหม่ |
request_id | string | ตัวระบุการเข้าชม (UUID) ใช้ร่วมกันในทุกเฟสของการเข้าชมนั้น |
phase | string | primary, late, correction ส่วน account_takeover และ account_assessment มี beacon |
visitor_id | string | ตัวระบุผู้เข้าชมที่คงที่ |
linked_id | string | ตัวระบุที่เชื่อมโยงซึ่งไคลเอนต์ส่งมา |
tag | string | แท็กกำหนดเองที่ไคลเอนต์ส่งมา |
fields | object | ฟิลด์กำหนดเองที่แนบกับการเข้าชมตอนเก็บข้อมูล มีเฉพาะสิ่งที่มาพร้อมกับการเข้าชม — ฟิลด์ที่แนบภายหลังผ่าน Data API จะไม่ปรากฏใน webhook ไม่ว่าจะเป็นเฟสหรือการลองใหม่ใด คีย์และค่าเป็นสตริง จะไม่มีเมื่อการเข้าชมไม่ได้นำฟิลด์มา |
timestamp | string | เวลาของเหตุการณ์ (RFC 3339) |
url | string | URL ของหน้าที่บันทึกเหตุการณ์ไว้ |
ip | string | ที่อยู่ IP ของไคลเอนต์ |
user_agent | string | สตริง user-agent ดิบของไคลเอนต์ |
browser.name / .version | string | เบราว์เซอร์ที่ตรวจพบ |
os.name / .version | string | ระบบปฏิบัติการที่ตรวจพบ |
device | string | ประเภทอุปกรณ์ (เช่น desktop, mobile) |
gpu | string | รุ่นของการ์ดแสดงผลตามที่เบราว์เซอร์รายงาน (WebGL) ผ่านการทำให้เป็นชื่อที่อ่านได้ (Intel Iris Xe Graphics, Apple M1 Pro) และเป็น Software renderer สำหรับตัวเรนเดอร์ด้วยซอฟต์แวร์ จะไม่มีเมื่อไม่ทราบ |
geo | object | ตำแหน่งตาม IP: country, city, lat, lon, timezone |
network | object | vpn, proxy, tor, datacenter (บูลีน) และ connection_type |
network.proxy_detected | boolean | ทราฟฟิก HTTP ของการเข้าชมกับเส้นทางเครือข่ายดิบของมันออกผ่านคนละเครือข่ายกัน นั่นคือมีพร็อกซีหรือ VPN อยู่หน้าเบราว์เซอร์ ที่อยู่สองรายการของผู้ให้บริการเดียวกัน (NAT ของผู้ให้บริการเครือข่าย หรือทางออกที่สองของ VPN เดียวกัน) ไม่นับ |
bot.result | string | human, bot หรือ uncertain |
bot.type | string | ชื่อบอต เอเจนต์ AI ที่ยืนยันแล้ว หรือป้ายกำกับกลุ่มเมื่อตรวจพบบอต |
bot.score | number | คะแนนบอต (0–100) |
identification.confidence | number | ความเชื่อมั่นของการระบุตัวตน (0.0–1.0) |
identification.incognito | boolean | บริบทการท่องเว็บแบบส่วนตัว/ไม่ระบุตัวตน |
decision.action | string | real, fake หรือ suspicious |
decision.risk_score | number | คะแนนความเสี่ยงรวม (0–100) |
extensions | array | ส่วนขยายเบราว์เซอร์ที่ตรวจพบในการเข้าชมนี้ ในรูป slug ที่คงที่สำหรับเครื่อง (ชื่อ หมวดหมู่ และลิงก์สโตร์อยู่ในแดชบอร์ดและ Data API) จะไม่มีเมื่อไม่พบสิ่งใด |
Pro ขึ้นไป — ผู้เข้าชมมีพฤติกรรมอย่างไรเมื่อเวลาผ่านไป
{ "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 ขึ้นไป — เหตุใดคำตัดสินจึงออกมาเช่นนั้น
{ "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, // ที่อยู่สาธารณะที่สังเกตได้บนเส้นทางเครือข่ายดิบ กล่าวคือที่อยู่ซึ่งอยู่หลังพร็อกซีหรือ // VPN จะไม่มีเมื่อไม่พบที่อยู่เช่นนั้น "real_ip": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" } }, "geo": { "isp": "Deutsche Telekom" }, // สภาพแวดล้อมเดสก์ท็อปที่ วัดได้ บนเครื่อง Linux ("Mint 22+", "Ubuntu", // "GNOME", "KDE") เพราะ User-Agent บอกดิสทริบิวชันไม่ได้ จะไม่มีเมื่อระบุไม่ได้ // ซึ่งเป็นกรณีของการเข้าชม Linux ส่วนใหญ่ และของทุกการเข้าชมที่ไม่ใช่ Linux "os_environment": "Mint 22+", // สิ่งที่การเข้าชมอ้างเกี่ยวกับตัวเอง เทียบกับสิ่งที่การตรวจสอบอิสระวัดได้ // จะมีเฉพาะเมื่อตรวจพบการปลอมแปลงจริงเท่านั้น ฟิลด์ `real` ที่ว่างหมายถึง // "การตรวจสอบเงียบ" ไม่ใช่ "ยืนยันแล้ว" สำหรับแกน `gpu` ฟิลด์ `claimed.gpu` // จะบรรจุการ์ดที่ถูกอ้างด้วยชื่อรุ่นที่อ่านได้แบบเดียวกับฟิลด์ `gpu` ระดับบนสุด "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"] } }, // ข้อเท็จจริงของอุปกรณ์ คือสิ่งที่เบราว์เซอร์ของผู้เข้าชมรายงานเกี่ยวกับตัวเครื่อง // ผ่านการชำระข้อมูลฝั่งเราแล้ว screen: ความละเอียด ความลึกสี และ device pixel // ratio; locale: ภาษาที่เบราว์เซอร์เองต้องการและเขตเวลาของมัน ต่างจาก // geo.timezone ที่อนุมานจากที่อยู่ IP // ความไม่ตรงกันของสองค่านี้เป็นสัญญาณที่พบบ่อยของตำแหน่งที่ถูกปลอม; client_hints: // User-Agent Client Hints คือสถาปัตยกรรมและบิตเนสของซีพียู รุ่นอุปกรณ์ // (Android: รหัสรุ่นใน `model` เช่น "SM-A556B" // และชื่อทางการตลาดจากรายการอุปกรณ์ Google Play ใน `device_name` เช่น // "Samsung Galaxy A55 5G") และเวอร์ชันแพลตฟอร์มที่แน่นอน // มีเฉพาะเบราว์เซอร์ที่ใช้ Chromium เท่านั้นที่รายงาน // ทุกบล็อกจะไม่มีเมื่อการเข้าชมไม่ได้นำข้อมูลเช่นนั้นมา // จึงควรถือว่าแต่ละบล็อกเป็นทางเลือก "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" }, // จะมีเฉพาะเมื่อการ์ดแสดงผลระบุตัวเองว่าเป็นการ์ดเสมือน ส่วน hypervisor // เป็นพจนานุกรมปิด (vmware, virtualbox, parallels, qemu, hyperv, bochs, // intel-gvt, vgpu) การไม่มีบล็อกนี้หมายความว่าไม่มีหลักฐานเช่นนั้น "environment": { "virtual_machine": true, "hypervisor": "vmware" }, "decision": { "suspect_score": 4.1 }, "guidance": { "version": 3, "overall": "review" } // see below}ดูชุดคำศัพท์ของรหัสเหตุผลและความหมายของ severity ได้ที่
การตรวจจับบอต
ฟิลด์สำหรับการใช้หลายบัญชี (Business ขึ้นไป) เมื่อเปิดโหมดบุคคลให้ workspace ของคุณ
แล้ว เนื้อหาจะมี person_id, บล็อก reputation (score, categories, overall_risk),
linked_accounts_count และ linked_visitors_count ด้วย — บอกว่าผู้เข้าชมเชื่อมโยงกับ
ผู้เข้าชมและบัญชีอื่นหรือไม่ ปัจจุบันชั้นข้อมูลระดับบุคคลทำงานในโหมดสังเกตการณ์ และจนกว่า
จะเปิดใช้จริง ฟิลด์เหล่านี้จะไม่ปรากฏในเนื้อหา อย่ากำหนดให้เป็นฟิลด์บังคับในสคีมาของคุณ
guidance นำคำแนะนำสำเร็จรูปว่า "ควรทำอย่างไร" มาให้ในแต่ละจุดผสานรวม คุณจึงไม่ต้อง
อนุมานนโยบายเองจากคะแนนดิบ บล็อกนี้มีอยู่ในทุกเหตุการณ์ v3 ของ Business ขึ้นไป —
allow จะถูกระบุไว้อย่างชัดเจน
{ "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 }}ทุกสถานการณ์เริ่มต้นที่ allow และเลื่อนขึ้นบันไดได้ทางเดียวเท่านั้น
allow → challenge → review → deny ภายในสถานการณ์หนึ่ง แกนที่เข้มงวดที่สุดที่
ถูกจุดชนวนจะชนะ และ overall คือค่าที่เข้มงวดที่สุดจากทั้งสี่สถานการณ์
| คำแนะนำ | การชำระเงิน | การสมัคร | การเข้าสู่ระบบ | พันธมิตร |
|---|---|---|---|---|
allow | ดำเนินการได้เลย | สร้างบัญชีได้เลย | ให้เข้าได้ | ให้เครดิตคอนเวอร์ชัน |
challenge | 3-D Secure / การยืนยัน | แคปต์ชา ยืนยันทางอีเมลหรือโทรศัพท์ | 2FA เพิ่มระดับ ยืนยันตัวตนซ้ำ | ทำเครื่องหมายว่าน่าสงสัยจนกว่าจะเห็นกิจกรรมจริง |
review | ดำเนินการ แต่เข้าคิวเพื่อตรวจสอบ | สร้างบัญชีโดยมีข้อจำกัด | ให้เข้าได้ แต่แจ้งเตือนไว้ | ระงับการจ่ายจนกว่าจะตรวจสอบเสร็จ |
deny | อย่าดำเนินธุรกรรม | ปฏิเสธการสร้างบัญชี | อย่าให้เข้า | อย่าให้เครดิตคอนเวอร์ชัน |
basis แสดงรายการแกนที่กำหนดคำแนะนำ — แกนที่เสนอคำแนะนำอ่อนกว่าคำแนะนำที่ถูกเลือก
จะไม่อยู่ในรายการ ให้อ่านตามรหัส แทนที่จะสันนิษฐานว่าเป็นชุดตายตัว
แกน (basis) | ความหมาย |
|---|---|
list | ผู้เข้าชมตรงกับรายการหนึ่งของ workspace คุณในโหมด enforce รายการ allow จะมีผลเหนือทุกแกน: ทุกสถานการณ์กลายเป็น allow และ basis เป็น ["list"] พอดี |
rule | การเข้าชมตรงกับกฎหนึ่งของ workspace คุณในโหมด enforce กฎจะเลื่อนเฉพาะคำแนะนำของสถานการณ์ของตัวเอง (กฎ allow ผ่อนเฉพาะสถานการณ์นั้น) และจะถูกข้ามเมื่อรายการได้ทำงานไปแล้ว |
bot | ตัวตรวจจับบอตสรุปว่านี่คือระบบอัตโนมัติ — ปฏิเสธทุกสถานการณ์ |
risk | คำตัดสินและคะแนนความเสี่ยงรวมของการเข้าชม |
behavior | ไบโอเมตริกเชิงพฤติกรรมบอกว่าเป็นบอตด้วยความเชื่อมั่นเพียงพอ — เพิ่มขั้นตอนยืนยันแทนการปฏิเสธ |
account_takeover | พฤติกรรมภายใต้บัญชีนี้ไม่ตรงกับโปรไฟล์ของเจ้าของอีกต่อไป — การเข้าสู่ระบบเป็น challenge การชำระเงินเป็น review |
network | Tor หรือ VPN ที่ออกมาจากศูนย์ข้อมูล |
affiliate_pattern | รูปแบบของการแนะนำที่สร้างขึ้นเอง — คอนเวอร์ชันของพันธมิตรถูกระงับไว้เป็น challenge |
fraud_reputation, multiaccounting | ชื่อเสียงและการจัดกลุ่มบัญชีของบุคคลเบื้องหลังผู้เข้าชม จะปรากฏเมื่อชั้นข้อมูลระดับบุคคลเปิดใช้จริง |
version คือเวอร์ชันของชุดกฎ ซึ่งจะเพิ่มขึ้นเมื่อตรรกะได้รับการปรับปรุง จึงควรบันทึกไว้
ใน log ของคุณ เวอร์ชัน 2 เพิ่มแกน list ซึ่งเป็นแกนแรกที่ผ่อนคำแนะนำได้ เวอร์ชัน 3 ย้าย
เกณฑ์การเข้าสู่ระบบของ account_takeover ไปอยู่บนสเกลของคะแนนคำตัดสินการยึดบัญชี
การเข้าชมเดียวกันจึงอาจได้ deny ในที่ที่เวอร์ชัน 2 ให้ challenge และกลับกัน
guidance เป็นแบบเพิ่มพูน สถานการณ์ใหม่จะมาในรูปคีย์ใหม่โดยไม่ทำลายสัญญาเดิม เฟสที่มา
ทีหลังจะชนะ ยกเว้นคำแนะนำบางส่วน การส่งที่คำนวณจากชุดข้อมูลนำเข้าที่ยังไม่ครบจะถูก
ทำเครื่องหมาย "partial": true และคำแนะนำบางส่วนจะไม่ไปแทนที่คำแนะนำที่สมบูรณ์
ซึ่งได้รับมาก่อนสำหรับ request_id เดียวกัน ในการส่งตามปกติ ฟิลด์ partial จะไม่ปรากฏเลย
เกณฑ์ที่แน่นอนถูกจงใจไม่บันทึกไว้ในเอกสาร คำแนะนำที่ถูกย้อนรอยกลับไปเป็นคะแนนได้ ย่อม
หมดสภาพความเป็นเครื่องป้องกัน มีข้อยกเว้นที่จงใจเพียงหนึ่งเดียว:
account_assessment.verdict.threshold ซึ่งเป็นเส้นแบ่งการตัดสินของคำตัดสินการยึดบัญชี
ที่อธิบายไว้ด้านล่าง
account_takeoverเฉพาะ Business และ Enterprise บนแพ็กเกจที่ต่ำกว่าจะไม่มีการสร้างการส่ง แม้ webhook จะ
สมัครรับเหตุการณ์นี้ไว้ก็ตาม เนื้อหาคือซองข้อมูลการระบุตัวตนครบชุดตามแพ็กเกจของคุณ
บวกกับบล็อก account_alert และบล็อก account_assessment ที่อธิบายในหัวข้อถัดไป
ส่งได้มากที่สุดหนึ่งครั้งต่อการเข้าชมหนึ่งครั้ง
{ "version": 3, "event": "account_takeover", "event_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31:ato", "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "phase": "beacon", // การแจ้งเตือนถูกจุดขึ้นจากบีคอน มีเพียง event_id ที่บอกว่า "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}เหตุใด type จึงมีเวอร์ชัน โครงสร้างเบื้องหลังคะแนนเคยเปลี่ยนมาแล้ว ได้แก่สเกลและ
ฐานของคำตัดสิน behavior-drift/2 คือโครงสร้างแรกที่มีเวอร์ชัน หากคุณปรับเทียบกฎของ
คุณเองจากตัวเลขนี้ ให้ถือว่าการเปลี่ยนค่านี้เป็นสัญญาณให้ตรวจกฎเหล่านั้นใหม่
account_alert.drift_score และ identification.drift_score เป็นตัวเลขเดียวกัน
(ตั้งแต่ 2026-10-03): คือคะแนนพฤติกรรมที่ผ่านการปรับให้เรียบซึ่งใช้ตัดสิน โดยวัดเทียบกับ
วงของหน้าต่างเวลาที่เชื่อถือได้ในอดีตของเจ้าของบัญชี ก่อนวันนั้น
identification.drift_score เป็นระยะทางดิบบนสเกลอื่น — อย่าผสมค่าข้ามวันนั้น เมื่อ
หน้าต่างเวลาใดไม่ได้รับการตัดสิน ฟิลด์จะไม่ปรากฏแทนที่จะเป็นศูนย์ ให้ใช้
account_assessment.verdict เมื่อคุณต้องการตัวเลขพร้อมเกณฑ์ที่ใช้เปรียบเทียบ
ใน v1 บล็อกนี้คือ accountAlert ที่มี type, linkedId และ drift และไม่มีบล็อก
การประเมิน ใน v2 การแจ้งเตือนเปลี่ยนชื่อสองฟิลด์ — linkedId → accountId และ
drift → driftScore — และเพิ่ม accountAssessment ส่วน v3 ใช้ชื่อแบบ snake_case
ข้างต้น ให้อัปเดต handler ของคุณเมื่อสลับเวอร์ชัน มิฉะนั้นตรรกะเรื่องการยึดบัญชีของคุณ
จะเงียบ ๆ มองไม่เห็นข้อมูลอีกต่อไป
account_assessmentเฉพาะ Business และ Enterprise ต้องสมัครรับโดยระบุชื่ออย่างชัดเจน: * ไม่รวม
เหตุการณ์นี้
หากไม่มีเหตุการณ์นี้ การไม่มีการแจ้งเตือนจะมีสองความหมายในคราวเดียว: เราประเมินเซสชัน เทียบกับโปรไฟล์ของเจ้าของบัญชีแล้วและตรงกัน หรือเราประเมินไม่ได้เลย — โปรไฟล์ของ เจ้าของยังไม่พร้อม หน้าต่างเวลามีข้อมูลน้อยเกินไป หรือทราฟฟิกไม่ใช่คน ทั้งสองกรณีดู เหมือนความเงียบ และความเงียบถูกอ่านว่าปลอดภัย ซึ่งมักไม่เป็นเช่นนั้น: ในวันทำงานเต็มวัน บนระบบจริงในเดือนกันยายน 2026 เรางดให้คำตัดสินใน 73% ของหน้าต่างเวลาและ 80% ของบัญชี กระบวนการที่สร้างบน "ไม่มีการแจ้งเตือนแปลว่าปกติ" จึงสร้างบนสถานะที่เราส่วนใหญ่ไม่มี
{ "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 ไม่เคยหายไปและไม่เคยว่าง
state | ความหมาย | ควรทำอย่างไร |
|---|---|---|
assessed_clear | ตัดสินแล้ว พฤติกรรมตรงกับเจ้าของ | ไม่ต้องทำอะไร |
assessed_alert | ตัดสินแล้ว พฤติกรรมเบี่ยงเบน | ยืนยันตัวตนเพิ่ม — การแจ้งเตือน account_takeover มีคำตัดสินเดียวกัน |
not_assessed | ไม่มีการให้คำตัดสิน | อย่าถือว่าปกติ ให้กลับไปใช้สัญญาณของคุณเอง |
reason (เฉพาะกับ not_assessed)
reason | ความหมาย |
|---|---|
profile_not_ready | ข้อมูลอ้างอิงของเจ้าของยังให้คำตัดสินสำหรับบัญชีนี้ไม่ได้ในขณะนี้ ให้ถือว่าเป็นสถานะ ไม่ใช่ขั้นตอน: อาจไม่เปลี่ยนเลย จึงไม่ควรรอ |
profile_learning | ข้อมูลอ้างอิงยังอยู่ระหว่างการสะสม |
window_too_sparse | มีคนอยู่ แต่หน้าต่างเวลามีปฏิสัมพันธ์น้อยเกินไป |
not_computable | หน้าต่างเวลาและข้อมูลอ้างอิงไม่มีแกนที่ให้ข้อมูลร่วมกัน |
bot_traffic | มีข้อมูลเพียงพอ แต่ไม่มีคนให้ตัดสิน |
ตั้งแต่ 2026-10-04 18:00 UTC จะไม่มีการออก window_too_sparse และ not_computable
อีกต่อไปสำหรับเจ้าของที่ข้อมูลอ้างอิงพร้อมแล้ว — หน้าต่างเวลาเช่นนั้นจะถูกรายงานเป็น
profile_not_ready เหตุผลทั้งสองยังอยู่ในรายการเพราะการส่งก่อนหน้านี้มีค่าเหล่านี้: หาก
handler ของคุณแยกทางตามค่าเหล่านี้ ให้ส่ง profile_not_ready ไปทางเดียวกัน และอย่า
เปรียบเทียบอัตราของเหตุผลเหล่านี้ข้ามวันนั้น
state และ reason ไม่จำกัดตามแพ็กเกจ ส่วน verdict เป็น Business ขึ้นไป เช่นเดียวกับ
drift_score
บล็อก verdict มีเฉพาะกับ assessed_clear และ assessed_alert เมื่อเป็น
not_assessed บล็อกนี้จะไม่ปรากฏแทนที่จะเติมด้วยศูนย์ — score เป็น 0 คู่กับ
threshold เป็น 0 จะถูกอ่านว่า "พฤติกรรมตรงกันพอดี"
| ฟิลด์ | ความหมาย |
|---|---|
severity | low หรือ high — สองระดับโดยจงใจ: ระดับกลางจะต้องมีเกณฑ์ที่สองบนสเกลเดียวกัน |
score | ค่าที่ใช้ตัดสิน |
threshold | ค่าที่ใช้เปรียบเทียบจริง |
scale | วิธีสร้างคะแนน: manhattan-normalized คือ d/(1+d) ในพื้นที่พฤติกรรมที่ทำให้เป็นมาตรฐานแล้ว โดย 0 หมายถึงตรงกันพอดี |
basis | ring — ระยะทางไปยังหน้าต่างเวลาที่เชื่อถือได้ในอดีตของเจ้าของที่ใกล้ที่สุด, centroid — ระยะทางไปยังโปรไฟล์เฉลี่ยของเจ้าของ |
windows | จำนวนหน้าต่างเวลาที่ถูกรวมเข้าในคะแนน |
score และ threshold อยู่บนสเกลเดียวกันที่มีชื่อกำกับเสมอ คู่ค่านี้จึงจำลองการตัดสินได้:
การแจ้งเตือนจะเกิดขึ้นเมื่อ score > threshold พอดี
มาถึงบ่อยแค่ไหน ส่งได้มากที่สุดหนึ่งครั้งต่อสถานะต่อการเข้าชม — ในทางปฏิบัติคือหนึ่งครั้ง
ต่อการเข้าชมที่มี linked_id ไม่ใช่หนึ่งครั้งต่อหน้าต่างเวลาเชิงพฤติกรรม สถานะอาจเปลี่ยน
ภายในการเข้าชมเดียว — เช่น ข้อมูลอ้างอิงเรียนรู้เสร็จ และ not_assessed กลายเป็น
assessed_clear — แล้วคุณจะได้รับทั้งสองตามลำดับ การเปลี่ยนนั้นเป็นไปได้ ไม่ใช่สิ่งที่สัญญา:
profile_not_ready อาจคงอยู่ตลอดอายุของบัญชี
สิ่งที่ไม่ได้สัญญา บัญชีที่ได้รับการปกป้องดีที่สุดคือผู้ใช้ที่ใช้งานอยู่ที่มีเซสชันยาวและ มีโปรไฟล์อ้างอิงครบ บัญชีที่ได้รับการปกป้องแย่ที่สุดคือผู้ใช้ที่ใช้งานไม่บ่อย ซึ่งการถูกยึดบัญชี อันตรายกว่า นี่คือคุณสมบัติของโครงสร้าง ไม่ใช่เกณฑ์ที่ปรับแต่งได้ เหตุการณ์นี้รายงาน ความเบี่ยงเบนเชิงพฤติกรรมจากข้อมูลอ้างอิง — ไม่ได้อ้างว่าจับการยึดบัญชีได้โดยทั่วไป
attack_detectedการแจ้งเตือนบอตพุ่งสูงมาในซอง v3 ร่วมพร้อมบล็อก attack โดยไม่มีฟิลด์การระบุตัวตน —
นี่เป็นเหตุการณ์ระดับ 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 บนเพย์โหลดเวอร์ชัน 1 จะได้รับสคีมาการแจ้งเตือนแบบแบนตามเดิม — สคีมานั้นไม่ เปลี่ยนแปลง
login_risk, trusted_device_enrolled และ trusted_device_revokedสามเหตุการณ์นี้มาจากการเรียกของคุณเองไปยัง API ความเสี่ยงการเข้าสู่ระบบ
(POST /v2/logins/assess) และอุปกรณ์ที่เชื่อถือได้ของมัน จะถูกส่งเฉพาะเมื่อสมัครรับ
โดยระบุชื่ออย่างชัดเจน — * ไม่รวมเหตุการณ์เหล่านี้ — และเฉพาะไปยัง webhook บน
เพย์โหลดเวอร์ชัน 2 หรือ 3 เท่านั้น เวอร์ชัน 1 ไม่มีเนื้อหาสำหรับเหตุการณ์เหล่านี้
login_risk จะถูกส่งเมื่อการประเมินคืนค่า challenge หรือ review — ไม่ส่งเมื่อเป็น
allow, deny หรือ not_assessed บล็อก login_risk คือการประเมินที่จัดเก็บไว้
ตรงตามที่การตอบกลับแบบซิงโครนัสให้มา Event id: login-risk:<id> โดย <id> คือ
ตัวระบุของเราสำหรับระเบียนที่จัดเก็บไว้ — ให้นำ assessment_id ของคุณเองจากเนื้อหาtrusted_device_enrolled และ trusted_device_revoked จะถูกส่งในการลงทะเบียน
การต่ออายุ และการเพิกถอนอุปกรณ์ที่เชื่อถือได้ทุกครั้ง บล็อก trusted_device —
user_id, device_ref, enrolled_at, expires_at, revoked_at, revoked_by
(api หรือ dashboard) — คือภาพสแนปช็อต ณ เวลาที่เกิดเหตุการณ์ เมื่อเป็น
trusted_device_enrolled ค่า revoked_at และ revoked_by จะเป็น null Event id:
trusted-device:<n>เหตุการณ์เหล่านี้ถูกส่งแบบอะซิงโครนัส ภายในราวห้าวินาทีหลังการเรียก และไม่มีเฮดเดอร์
X-Tracio-Request-Id — ให้ใช้ login_risk.request_id เพื่อหาการเข้าชม webhook เป็น
การแจ้งเตือนสำหรับการตรวจสอบย้อนหลังและการเตือนภัย ส่วนการตัดสินใจจริงคือการตอบกลับ
แบบซิงโครนัส
{ "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_crossedส่งเฉพาะเมื่อสมัครรับโดยระบุชื่ออย่างชัดเจน — * ไม่รวมเหตุการณ์นี้ — และเฉพาะไปยัง
webhook บนเพย์โหลดเวอร์ชัน 2 หรือ 3 ทุก 15 นาที TRACIO จะคำนวณคะแนนคุณภาพของ
พันธมิตรแต่ละรายที่มีข้อมูลเพียงพอใหม่ และส่งเหตุการณ์เมื่อคำแนะนำแย่ลง: none → review,
none → hold หรือ review → hold การดีขึ้นจะไม่ส่งอะไร และทำให้การแย่ลงครั้งถัดไปถูก
รายงานได้อีกครั้ง เหตุการณ์นี้เป็นของ workspace: จะไปยัง webhook ที่สมัครรับไว้ของทุก
environment และไม่มีเฮดเดอร์ 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 คือสัดส่วนของพันธมิตรในช่วง 90 วันล่าสุด ชุดเดียวกับที่รายงานในแดชบอร์ดแสดง:
สัดส่วนการสมัครที่ไปถึงการฝากเงินครั้งแรก (reg_to_ftd) สัดส่วนผู้เล่นที่ยังใช้งานอยู่
14 วันหลังการสมัคร (active14_share) และสัดส่วนของคอนเวอร์ชันที่มีอุปกรณ์หรือบุคคลซ้ำ
มาจากเครือข่ายฉ้อโกง มาจากนักล่าโบนัส มาจากศูนย์ข้อมูล VPN หรือบอต ถูกติดป้ายว่าเป็น
chargeback หรือการฉ้อโกงของพันธมิตร และที่ไม่พบการเข้าชม TRACIO เพียงให้คำแนะนำ —
การระงับเองและการตัดสินใจเรื่องการจ่ายเงินยังอยู่ในแพลตฟอร์มพันธมิตรของคุณ บนเพย์โหลด
เวอร์ชัน 2 เนื้อหาเดียวกันใช้คีย์แบบ camelCase: บล็อกคือ affiliateThreshold พร้อม
affiliateId, previousRecommendation, qualityScore และอื่น ๆ
transaction_state_changedBusiness ขึ้นไป ส่งเมื่อผลลัพธ์ของธุรกรรมที่คุณส่งไปยัง POST /v2/transactions
เปลี่ยนไป: กฎตัดสิน REVIEW หรือ DECLINE (APPROVE จะไม่ส่งอะไร) นักวิเคราะห์ส่ง
ธุรกรรมไปตรวจสอบในแดชบอร์ด หรือมีการตั้ง แทนที่ หรือลบป้ายกำกับ ใช้เหตุการณ์นี้เพื่อระงับ
โบนัสระหว่างที่คำขอรับอยู่ระหว่างการตรวจสอบ และเพื่อยึดหรือปล่อยโบนัสเมื่อผลลัพธ์ได้ข้อยุติ
โดยไม่ต้องคอยสอบถาม * รวมเหตุการณ์นี้ และจะถูกส่งเฉพาะไปยัง webhook บนเพย์โหลด
เวอร์ชัน 2 หรือ 3 webhook ที่ผูกกับ environment หนึ่งจะได้รับเหตุการณ์นี้เฉพาะสำหรับ
environment นั้น
{ "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 }}| คีย์ | ความหมาย |
|---|---|
effective_state | ผลลัพธ์ที่ควรดำเนินการ: สถานะของนักวิเคราะห์หรือของป้ายกำกับเมื่อมีการตั้งไว้ มิฉะนั้นคือ state |
recommendations | สิ่งที่ควรทำกับโบนัส อนุมานจาก effective_state |
label, label_source, label_at | ป้ายกำกับปัจจุบัน ตั้งไว้ที่ไหน (api หรือ dashboard) และเมื่อใด ทั้งสามเป็น null เมื่อธุรกรรมไม่มีป้ายกำกับ |
previous_effective_state | effective_state ก่อนการเปลี่ยนแปลงนี้ เป็น null สำหรับเหตุการณ์ที่ส่งจากการตัดสินอัตโนมัติ |
บล็อกนี้แสดงธุรกรรมตามสภาพ ณ ตอนที่ส่งเหตุการณ์: หากมีการเปลี่ยนแปลงสองครั้งติดกัน
อย่างรวดเร็ว ทั้งสองเหตุการณ์จะมีสถานะล่าสุด โดยแต่ละเหตุการณ์มี
previous_effective_state และ timestamp ของตัวเอง ไม่รับประกันลำดับ — ให้เรียง
ตาม timestamp และอ่านธุรกรรมผ่าน API เพื่อดูสถานะปัจจุบัน Event id:
txn-state:<id>:created สำหรับการตัดสินอัตโนมัติ และ txn-state:<id>:<n> สำหรับการ
เปลี่ยนแปลงครั้งต่อ ๆ มาแต่ละครั้ง เหตุการณ์นี้ไม่บอกว่าใครตั้งป้ายกำกับ — บอกเพียงว่ามา
จากที่ใด
reputation_changedBusiness ขึ้นไป ส่งเมื่อชื่อเสียงข้ามลูกค้าของบุคคลที่องค์กรของคุณเคยเห็นเลื่อนไปอยู่
อีกระดับหนึ่ง (เช่น clean → suspicious) ต้องอาศัยชั้นข้อมูลระดับบุคคล ซึ่งปัจจุบัน
ทำงานในโหมดสังเกตการณ์ — เหตุการณ์จึงเกิดขึ้นเฉพาะกับ workspace ที่เปิดใช้การเชื่อมโยง
ตัวตนข้ามอุปกรณ์ นี่เป็นเหตุการณ์ระดับบุคคล: ไม่มี request_id และไม่มีบล็อกของการเข้าชม
{ "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 }}คีย์ของบล็อกคือ reputation_change ไม่ใช่ reputation: คีย์หลังถูกใช้สำหรับชื่อเสียง
ในบริบทของการเข้าชมหนึ่งครั้งโดยเฉพาะ และทั้งสองเป็นคนละสิ่งกัน สคีมา 1 ส่งรูปแบบแบน
ตามเดิม — ฟิลด์เดียวกันที่ระดับบนสุด
การส่งทุกครั้งจะมีเฮดเดอร์ X-Tracio-Signature มาด้วย
X-Tracio-Signature: t=1710432000,v1=5257a869e7ecebed…t คือ Unix timestamp (หน่วยวินาที) ตอนที่คำขอถูกลงลายเซ็นv1 คือ HMAC-SHA256 ของ "<t>.<rawRequestBody>" ในรูปเลขฐานสิบหก โดยใช้ secret
ของ webhook เป็นคีย์timestamp เป็นส่วนหนึ่งของเนื้อหาที่ถูกลงลายเซ็น จึงให้การป้องกันการเล่นซ้ำ
มีสองเรื่องที่ต้องทำให้ถูก ไม่เช่นนั้นการตรวจสอบจะล้มเหลวบนระบบจริง
v1= ตัวใดตัวหนึ่ง ระหว่างการหมุนเวียน secret เฮดเดอร์จะมี
ลายเซ็นสองชุด และตัวแยกวิเคราะห์ที่เก็บไว้เพียงชุดเดียวจะปฏิเสธการส่งที่ถูกต้อง
ตลอดทั้งช่วงการหมุนเวียน// 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")})การส่งตามสคีมา 2 และ 3 จะมี X-Tracio-Signature-Ed25519
(t=<unix>,kid=<id>,v1=<base64>) เพิ่มมาด้วย เนื่องจากทั้งสองฝ่ายรู้ secret ของ HMAC
ลายเซ็น HMAC จึงพิสูจน์ได้เพียงว่าผู้ส่งรู้ secret แต่ไม่ได้พิสูจน์ว่าคำขอมาจาก TRACIO
จริง ลายเซ็นแบบอสมมาตรทำหน้าที่นั้น คีย์สาธารณะเผยแพร่ไว้ที่
https://api.tracio.ai/.well-known/webhook-keys โดยจัดทำดัชนีตาม kid: ให้อ่านฟิลด์
public_key ชื่อแทนแบบเดิม publicKey มีค่าเดียวกันและจะถูกนำออกในวันที่ 2027-04-12
การส่งทดสอบจากแดชบอร์ดจะลงลายเซ็นด้วย HMAC เท่านั้น — คีย์ส่วนตัวของแพลตฟอร์มอยู่บน
โหนดที่ทำหน้าที่ส่ง และจงใจไม่เปิดให้แดชบอร์ดเข้าถึง ตัวตรวจสอบที่บังคับให้ต้องมี
Ed25519 ต้องปล่อยการส่งทดสอบผ่านไปได้ (การส่งทดสอบจะมีส่วนต่อท้าย :test ใน
event_id) มิฉะนั้นการทดสอบจากแดชบอร์ดจะล้มเหลวทั้งที่ระบบจริงทำงานปกติ ข้อควรระวัง
แบบเดียวกันนี้ใช้กับการตรวจรูปแบบด้วย การส่งทดสอบจะมี request_id ในรูป test_<hex>
และมี visitor_id เป็นสตริงตรงตัวว่า test_visitor ดังนั้น handler ที่ตรวจค่าเหล่านี้
เทียบกับรูปแบบของระบบจริงจะปฏิเสธการส่งที่ถูกต้องในด้านอื่น
หลังการหมุนเวียน secret ทั้งสองตัวจะยังใช้ได้อีก 24 ชั่วโมง และเฮดเดอร์จะมีลายเซ็นทั้ง สองชุด คุณจึงอัปเดตการตั้งค่าได้โดยไม่สูญเสียการส่ง การกด Revoke now จะย่นช่วงเวลา นี้ให้สั้นลง ให้อัปเดต secret ฝั่งคุณภายใน 24 ชั่วโมง เมื่อช่วงเวลาปิดลง secret เดิมจะ ไม่ตรงอีกต่อไป และหากปลายทางของคุณตอบกลับลายเซ็นที่ไม่ถูกต้องด้วยรหัส 4xx การตอบเช่นนั้น ห้าครั้งติดกันจะปิดใช้งาน webhook
| เฮดเดอร์ | คำอธิบาย |
|---|---|
Content-Type | application/json |
X-Tracio-Signature | t=<unix>,v1=<hmac_sha256_hex> — มี v1= สองชุดในช่วงการหมุนเวียน |
X-Tracio-Signature-Ed25519 | ลายเซ็นของแพลตฟอร์ม t=<unix>,kid=<id>,v1=<base64> (เฉพาะ v2 และ v3) |
X-Tracio-Event-Id | ตัวระบุการส่ง — คีย์สำหรับ idempotency |
X-Tracio-Request-Id | ตัวระบุการเข้าชม ตรงกับ request_id (เฉพาะ v2 และ v3) ส่งเฉพาะเหตุการณ์ของการเข้าชม — identification, account_takeover และ account_assessment |
X-Tracio-Event-Type | ประเภทของเหตุการณ์ (เฉพาะ v2 และ v3) |
X-Tracio-Delivery-Attempt | ลำดับครั้งที่พยายามส่ง เริ่มจาก 1 (เฉพาะ v2 และ v3) |
X-Tracio-Payload-Version | 3 หรือ 2 (เฉพาะ v2 และ v3) |
X-Tracio-Webhook-Id | ตัวระบุของ webhook ที่ทำให้เกิดการส่งครั้งนี้ |
การส่งอาจถูกลองใหม่ และการลองใหม่จะมี X-Tracio-Event-Id ค่าเดิม ให้ใช้ค่านี้ในการ
ขจัดรายการซ้ำ
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")})โปรดทราบว่า event_id ไม่ซ้ำกันในระดับเหตุการณ์ ไม่ใช่ระดับ webhook หากมี webhook
หลายตัวใน workspace สมัครรับเหตุการณ์เดียวกัน แต่ละตัวจะได้รับการส่งที่มีตัวระบุเดียวกัน
สำหรับ identification ค่านี้ถูกสร้างขึ้นเป็น <request_id>:<phase> สามเฟสของการเข้าชม
เดียวกันจึงถูกขจัดรายการซ้ำแยกจากกัน ไม่ยุบรวมเป็นรายการเดียว เหตุการณ์ที่ไม่มีการเข้าชม
มีรูปแบบของตัวเอง — ดูที่แต่ละเหตุการณ์ด้านบน
ให้ตอบกลับด้วย 2xx — นั่นคือสัญญาณเดียวที่บอกว่าการส่งได้รับการยอมรับ
| การตอบกลับ | เกิดอะไรขึ้น |
|---|---|
2xx | ส่งสำเร็จ |
429 Too Many Requests | ไม่นับเป็นความล้มเหลวและไม่ใช้โควตาการพยายาม ค่า Retry-After ที่ยาวกว่าจะถูกเคารพ |
408, 425, 5xx, การเชื่อมต่อหลุด | ลองใหม่โดยเว้นระยะนานขึ้นเรื่อย ๆ |
410 Gone | ถือว่าปลายทางถูกลบไปแล้ว — webhook จะถูกปิดใช้งานทันที |
4xx อื่น ๆ | ลองใหม่ แต่ห้าครั้งติดกันจะปิดใช้งาน webhook — 400/401/404 ไม่หายด้วยการลองใหม่ |
ตารางการลองใหม่: 5s → 30s → 2min → 10min → 30min → 2h → 6h (8 ครั้ง) การลองใหม่ช่วงแรก อยู่ภายในหนึ่งนาที การรีสตาร์ตบริการของคุณสั้น ๆ จึงไม่ทำให้คุณเสียการแจ้งเตือน แต่ละ ช่วงรอถูกสุ่มระหว่างครึ่งหนึ่งถึงค่าเต็ม เพื่อไม่ให้การลองใหม่ยิงพร้อมกันเป็นชุดเดียว หลังเหตุขัดข้อง
การปิดใช้งานอัตโนมัติ ต้องเข้าเกณฑ์ (ล้มเหลวติดกัน 20 ครั้ง หรือข้อผิดพลาดด้านการ ตั้งค่า 5 ครั้ง) และ ต้องล้มเหลวต่อเนื่องอย่างน้อย 15 นาที — การรีสตาร์ตสั้น ๆ จึงไม่ สามารถทำลายการผสานรวมได้ แม้จะมีการส่งค้างในคิวจำนวนมาก ช่วงว่างที่ยาวกว่า 15 นาทีจะ เริ่มนับใหม่ แดชบอร์ดจะแสดงเหตุผลพร้อมรหัสการตอบกลับและข้อความแสดงข้อผิดพลาด และมีปุ่ม Re-enable ที่รีเซ็ตตัวนับ
| แพ็กเกจ | จำนวน webhook ต่อ workspace |
|---|---|
| Free | ไม่มีให้ใช้งาน |
| Pro | 5 |
| Business | 20 |
| Enterprise | 100 |
ปลายทางต้องเป็น https ที่มี IP สาธารณะ — ที่อยู่ส่วนตัวและ loopback จะถูกปฏิเสธ
รวมถึงกรณีที่พบหลังการเปลี่ยนเส้นทาง — และเปลี่ยนเส้นทางได้ไม่เกินสองระดับ
ระบบจะตามเฉพาะการเปลี่ยนเส้นทาง 307 และ 308 เท่านั้น 301, 302 และ 303
สั่งให้ไคลเอนต์เปลี่ยนไปใช้ GET และทิ้งเนื้อหาคำขอ การส่งจึงไม่ตามไป และการพยายามครั้ง
นั้นถูกนับว่าล้มเหลว หากโหลดบาลานเซอร์ของคุณทำให้ URL เป็นรูปแบบมาตรฐาน (เติม www
หรือเครื่องหมายทับท้าย) ให้ชี้ webhook ไปที่ URL สุดท้ายโดยตรง
การจัดการ webhook ทำผ่านแดชบอร์ด โดยแดชบอร์ดขับเคลื่อน API การจัดการในขอบเขต
workspace ซึ่งให้บริการอยู่บนโฮสต์ของแอปพลิเคชัน (เช่น https://app.tracio.ai/api/v1)
และปลายทางที่แสดงด้านล่างคือสิ่งที่แดชบอร์ดเรียกใช้ ปลายทางของ webhook ทั้งหมดอยู่ภายใต้
/workspaces/{wsId}
นี่ไม่ใช่ช่องทางแบบเซิร์ฟเวอร์ถึงเซิร์ฟเวอร์ API การจัดการรับเฉพาะ JWT ของ เซสชันแดชบอร์ดของคุณ ซึ่งถูกตรวจเทียบกับบทบาทของคุณใน workspace (RBAC) ส่วนคีย์ ลับแบบ
tracio_sk_…จะถูกปฏิเสธที่นี่ เนื่องจากเซสชันนั้นอยู่ในเบราว์เซอร์และหมดอายุ ไปพร้อมกับเบราว์เซอร์ ให้มองการเรียกด้านล่างว่าเป็นคำอธิบายสิ่งที่แดชบอร์ดทำ มากกว่า จะเป็นการผสานรวมที่ควรทำเป็นอัตโนมัติ หากต้องการเข้าถึงด้วยโปรแกรมจากแบ็กเอนด์ของคุณ เอง ให้ใช้ Data API ซึ่งอ่านได้อย่างเดียว
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 สำหรับลงลายเซ็นสร้างขึ้นโดย TRACIO และถูกส่งกลับเพียงครั้งเดียวตอนสร้าง
(และตอนหมุนเวียน) ในฟิลด์ signingSecret ให้เก็บรักษาอย่างปลอดภัย — นี่คือคีย์ที่คุณ
ใช้ตรวจสอบลายเซ็น
{ "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" }}ในการอ่านครั้งถัดไป signingSecret จะถูกปิดบัง (null) — จะเปิดเผยเฉพาะตอนสร้างและ
ตอนหมุนเวียน secret เท่านั้น
| เมธอด | เส้นทาง | คำอธิบาย |
|---|---|---|
GET | /workspaces/{wsId}/webhooks | แสดงรายการ webhook |
PATCH | /workspaces/{wsId}/webhooks/{webhookId} | อัปเดต url / events / status |
DELETE | /workspaces/{wsId}/webhooks/{webhookId} | ลบ webhook |
POST | /workspaces/{wsId}/webhooks/{webhookId}/test | ส่งการส่งทดสอบที่มีลายเซ็น |
POST | /workspaces/{wsId}/webhooks/{webhookId}/secret/rotate | หมุนเวียน secret สำหรับลงลายเซ็น |
GET | /workspaces/{wsId}/webhooks/{webhookId}/deliveries | แสดงรายการการพยายามส่งล่าสุด |
ให้คืนค่า 2xx โดยเร็วที่สุด แล้วประมวลผลเพย์โหลดแบบอะซิงโครนัสเพื่อเลี่ยงการหมดเวลา
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) }}ใช้การกระทำ Test บน webhook (หรือ POST .../webhooks/{webhookId}/test) เพื่อส่ง
เพย์โหลดตัวอย่างที่มีลายเซ็นไปยังปลายทางของคุณ แล้วยืนยันว่าปลายทางเข้าถึงได้และ
ตรวจสอบลายเซ็นได้อย่างถูกต้อง
สำหรับการพัฒนาในเครื่อง ให้เปิดเซิร์ฟเวอร์ของคุณออกสู่ภายนอกด้วยทันเนล เช่น ngrok
ngrok http 3000# Use the generated URL as your webhook endpoint