TRACIO's bot detection identifies automated browsers, headless tools, and scripted attacks using a two-layer architecture: client-side collection in the browser agent and server-side analysis. It covers mainstream automation frameworks, headless browsers, AI browsing agents, and antidetect browsers with near-zero false positives, while automatically allowlisting legitimate search-engine crawlers.
The browser agent runs a broad set of checks and reports their outcomes. On the
server, the outcomes are weighted into a bot score, and the score maps to a
verdict. A small number of unambiguous indicators are decisive on their own and
force a bot verdict regardless of the rest.
Which checks run, how they are weighted, and where the thresholds sit are not published — on any plan. Documenting them would tell bot operators exactly what to change to get past the protection. What you receive instead is the verdict, a bot-type label, and — on Business and above — reason codes that name the class of observation behind the decision.
Every identification carries a bot verdict. It reaches you as bot.result in the
webhook payload and as result.bot in the client SDK.
| Result | Description |
|---|---|
human | No automation indicators found. Verified search-engine crawlers also resolve to human. |
bot | Automation or scripted access detected. A bot.type label accompanies it. |
uncertain | Mixed or weak signals — neither clearly human nor clearly automated. |
This maps to the business decision.action field (real, fake or suspicious)
used across the dashboard and the webhook payload.
bot.score accompanies the verdict. In webhook payload version 2 it is a 0..100
decimal — the same number the dashboard shows for that visit. (In the frozen
version 1 schema it is a 0..1 fraction; see Webhooks.)
bot.type is present when the result is bot. It is either the name of a
recognized bot or runtime, or a family when naming the specific check would
reveal detector internals:
bot.type | Meaning |
|---|---|
playwright, jsdom, electron | The named automation runtime was identified |
browser_use, claude_computer_use, skyvern, genspark, fellou | The named AI browsing agent was identified |
automation | Automation tooling, without naming the specific check |
headless | A browser running without a user interface |
antidetect | An antidetect / fingerprint-spoofing build |
extension | A browser extension driving automation, proxying or VPN |
privacy_browser | A privacy-hardened browser build (for example Mullvad Browser) |
other | Detected, but outside the published vocabulary |
The list is closed by design: a check added to the detector tomorrow surfaces as
other rather than leaking its internal name. Treat bot.type as a label to
branch on, not as an exhaustive enum to validate against — new named runtimes are
added over time.
On Business and Enterprise plans the webhook payload carries a reasons array —
at most eight entries, sorted by importance. A code names the class of
observation, never the check behind it:
| Code | What it means |
|---|---|
automation_signature | Traces of automation tooling |
headless_browser | A browser with no user interface |
anonymous_browser | A privacy-hardened or antidetect build made to look generic |
privacy_hardening | Aggressive privacy settings |
behavior_anomaly | Non-human interaction patterns |
fingerprint_tampering | Values that should agree with each other do not |
environment_anomaly | The runtime environment is internally inconsistent |
identity_mismatch | The declared identity disagrees with what is observed |
virtual_environment | An emulator rather than physical hardware |
strong_automation_evidence | Unambiguous bot indicators — decisive on its own |
detection_signal | Other checks that fired |
severity is high, medium or low, and it is the share of the verdict this
class accounts for on this visit — not a fixed property of the code. The same
code can arrive as high on one visit and low on another, so don't cache it as
a constant.
privacy_hardening deserves particular care: it fires for ordinary
privacy-conscious visitors as much as for evasion. Weigh it together with the rest
rather than acting on it alone.
virtual_environment comes from emulator detection. There is no separate "running
under a hypervisor" verdict on web: the clues a browser can see — a generic software
renderer, an unusually low core count — appear just as often on ordinary machines
with no GPU driver and inside remote-desktop sessions, so we do not turn them into a
verdict you would have to second-guess.
| Capability | Availability |
|---|---|
| Automation, headless and AI-agent detection | All plans |
| Verified-crawler allowlisting | All plans |
| Emulator detection | All plans |
Antidetect indicators (bot.antidetectScore, 0..100) | Pro and above |
| Reason codes, behavioral block | Business and above |
| DevTools detection | Business and above |
Antidetect scoring is still being calibrated.
bot.antidetectScoreis reported so you can observe it and tune your own policy — treat it as an input to your risk decision rather than as a standalone reason to block.
Mobile-only checks are not effective on web. Root, jailbreak, cloned/dual-app and Frida detection depend on native signals that a browser cannot collect. TRACIO ships browser SDKs only, so rely on the automation, headless, AI-agent, antidetect, emulator and behavioral verdicts, which are fully active on web.
Known good bots are recognized by their user-agent and then verified against the connecting IP — a reverse DNS lookup on the IP, a check that the resulting hostname belongs to the operator's domain, and a forward lookup confirming it resolves back to the same IP. This is the verification procedure the crawler operators themselves publish.
| Crawler | Verified domains |
|---|---|
.googlebot.com, .google.com | |
| Bing | .search.msn.com |
| Apple | .applebot.apple.com |
| Yahoo | .crawl.yahoo.net |
| Yandex | .yandex.com, .yandex.net, .yandex.ru |
| DuckDuckGo | .duckduckgo.com |
A verified crawler resolves to human. A request that claims to be Googlebot
while its IP demonstrably belongs to someone else is classified as a bot — a
merely missing or timed-out lookup is not treated as proof of spoofing.
Interaction dynamics — pointer movement, keyboard cadence, scrolling — feed the
verdict as evidence of whether a human is present, not of which human it is.
On Business and above the aggregate is delivered in the webhook behavior block:
{ "behavior": { "score": 87, "verdict": "human", "confidence": 0.92 }}score runs 0..100 (higher = more human), verdict is human, uncertain or
bot, and confidence (0..1) says how much evidence the verdict rests on — a
short visit with two pointer movements scores low here even when the verdict looks
decisive.
The block is present only when behavioral scoring actually ran for that visit. A missing block means "no data", never "nothing suspicious" — do not read its absence as a clean signal.
The client SDK exposes a convenience boolean, result.bot.detected, plus a
confidence on a 0..100 scale:
const result = await tracio.getResult()
if (result.bot.detected) { console.warn(`Bot detected (confidence ${result.bot.confidence})`) showCaptcha() return}
await login(credentials)On your server, act on the webhook delivery. The bot verdict
lives in the bot object — bot.result and bot.type:
// `event` is the webhook delivery body (/docs/webhooks)app.post("/webhook/tracio", async (req, res) => { const event = req.body
if (event.bot?.result === "bot") { await db.blockedRequests.insert({ visitorId: event.visitorId, botType: event.bot.type, ip: event.ip, timestamp: new Date(), }) }
res.status(200).send("OK")})Zero and empty values are omitted from the payload, so read bot.type defensively
rather than requiring it in your schema.
TRACIO's bot detection is designed for near-zero false positives, but edge cases exist:
| Scenario | Risk | Mitigation |
|---|---|---|
Browser extensions modifying navigator | Low | Cross-validation across many checks prevents single-check triggers |
| Corporate security software | Very Low | Headless classification requires corroborating evidence |
| Accessibility tools | None | Accessibility APIs do not feed bot detection |
| VPN/proxy users | None | Network flags are tracked separately from the bot verdict |
If you see false positives, review bot.type (and reasons on Business and
above) to understand which class of observation fired, then adjust your policy.
See Troubleshooting for the common causes.