Under the hood
AI Scoring Engine
Every verification request is scored 0.0 – 1.0, where higher means more human. A score of 0.70 or above is verified. The engine is an ensemble of three explainable modules — and every verdict ships with the reasons that produced it.
Module 1 — Behavioral biometrics
While a visitor uses your page, the widget collects anonymized interaction samples (never content): mouse movements and keystroke timings. The engine computes:
- Speed coefficient of variation — humans move with irregular speed; scripted cursors are suspiciously constant.
- Direction-change rate — real pointer paths turn constantly; synthetic ones are straight.
- Keystroke rhythm — intervals between keys for humans vary widely (CV ≈ 0.3–0.8); automation is metronome-steady.
Privacy
Module 2 — Bot signature detection
The user-agent and request shape are matched against a signature list: headless browsers (HeadlessChrome, Puppeteer, Playwright, PhantomJS), HTTP libraries (curl, wget, requests, axios, Go/Java clients) and common crawler markers. A match caps the score at 0.05 regardless of everything else.
Module 3 — Adaptive per-site baselines
This is the learning part. For every site the engine maintains an online model (EWMA) of normal traffic — the typical behavioral statistics your real visitors produce. Requests that deviate several standard deviations from the learned baseline are penalized, so a botnet that perfectly imitates “generic human” traffic still sticks out against your site's pattern.
- Baselines mature after ~20 observations and decay if traffic stops.
- They live in server memory — zero configuration, per-site isolation.
Supporting signals
| Signal | Effect |
|---|---|
| Browser headers (UA, Accept-Language, screen, timezone) | Small bonuses when consistent; penalties when thin or malformed |
| Origin / Referer matches the registered domain | +0.05; mismatch −0.15 (soft signal, never a hard block) |
| No behavioral data at all | Score capped at 0.65 — the widget must run for a full pass |
| Baseline deviation (z > 2.5) | Up to −0.2 anomaly penalty |
Risk levels
| Score | Risk level | Meaning |
|---|---|---|
| 0.70 – 1.00 | low | Verified — treat as human |
| 0.40 – 0.69 | medium | Gray zone — add your own fallback (retry, rate-limit, 2FA…) |
| 0.00 – 0.39 | high | Almost certainly automated |
Your server receives score and risk_level from verify-token, so you can enforce your own policy instead of the binary default.
Explainability
Each verification stores its reasons as structured data. In the dashboard's Verifications page, hover any risk badge to see them:
[
{ "code": "ua_present", "label": "Consistent browser User-Agent", "impact": 0.1 },
{ "code": "mouse_human", "label": "Human-like mouse entropy", "impact": 0.11 },
{ "code": "keystroke_human", "label": "Human-like keystroke rhythm", "impact": 0.09 },
{ "code": "origin_match", "label": "Origin matches registered domain", "impact": 0.05 }
]Roadmap: from heuristics to trained models
The current ensemble is deterministic and explainable by design — it needs no training data to be useful. As your instance accumulates labeled traffic, the natural next steps are:
- ONNX model serving — export verifications with reasons as a dataset, train a gradient-boosted tree or small MLP offline, and serve it in-process with ONNX Runtime (<5 ms per request, no external API, no data leaves your server).
- Reputation feeds — persist per-IP and per-ASN EWMA statistics for cross-session scoring.
- LLM-assisted tuning — periodic (not per-request) analysis of score distributions to recalibrate weights; per-request LLM calls are deliberately avoided — too slow and costly for verification volume.
The score, risk_level and reasons columns in the verifications table are already the training-ready dataset for this.