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

Raw samples are summarized into statistics server-side. Keystroke content is never captured — only the milliseconds between keys.

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

SignalEffect
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 allScore 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

ScoreRisk levelMeaning
0.70 – 1.00lowVerified — treat as human
0.40 – 0.69mediumGray zone — add your own fallback (retry, rate-limit, 2FA…)
0.00 – 0.39highAlmost 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:

reasons stored with a verification
[
  { "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.