TRACIO analyzes every visitor's IP address server-side. It flags VPNs, proxies, Tor exit nodes, and datacenter origins, and resolves city-level geolocation. All analysis runs server-side with no client-side dependencies.
IP intelligence reaches your application through the webhook payload: the
network object (VPN/proxy/Tor/datacenter booleans, connection type and, on Pro
and above, proxyDetected) and the geo object (country, city, coordinates,
timezone) are delivered on every webhook. ISP, ASN and the realIp object are
added on Business and above. See
Webhooks for the full payload shape and signature verification.
VPN, proxy, Tor, and datacenter classification is resolved from the connecting IP
using commercial IP intelligence databases (IP2Location for VPN/proxy/Tor type,
MaxMind for geolocation and ASN). Each of these four flags is surfaced as a single
boolean on the webhook network object — there is no per-method breakdown or confidence score in
the public payload.
| Flag | Field | Meaning |
|---|---|---|
| VPN | network.vpn | IP belongs to a known VPN provider |
| Proxy | network.proxy | IP is a datacenter or residential proxy |
| Tor | network.tor | IP is a known Tor exit node |
| Datacenter | network.datacenter | IP belongs to a cloud/hosting provider |
{ "network": { "vpn": true, "proxy": true, "tor": false, "datacenter": false, "connectionType": "VPN" }}// `payload` is the webhook delivery body (/docs/webhooks)if (payload.network.vpn) { // visitor is connecting through a VPN}Datacenter and residential proxies both surface as network.proxy; datacenter
origin is additionally reported as network.datacenter. Classification comes from
the IP2Location proxy database, which covers major cloud/hosting providers (AWS,
Google Cloud, Azure, Oracle Cloud, Alibaba Cloud, DigitalOcean, Hetzner, OVH,
Linode/Akamai, Vultr, and others).
{ "network": { "vpn": false, "proxy": true, "tor": false, "datacenter": true, "connectionType": "DCH" }}network.proxy classifies the address the connection arrived from. A separate
check compares the page traffic of a visit with its raw network path: when the
two leave through different networks, a proxy or VPN sits between the browser and
your site, and the visit is reported with network.proxyDetected: true. Two
addresses of the same provider — carrier NAT, a second exit of the same VPN — do
not count.
On Business and above the public address observed behind the proxy or VPN is
delivered next to the address the connection arrived from, with its country and
ISP, as network.realIp. It is absent when no such address was observed, so read
it with a guard (network.realIp?.country); country and isp are absent when
they could not be resolved.
{ "network": { "proxy": false, "proxyDetected": true, "realIp": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" } }}In webhooks both fields arrive under the same names on every
visit. The visitor summary of the Data API aggregates them as
network.proxyDetectedSeen (whether a proxy was ever in front) and
network.lastRealIp (the most recent address, as a plain string).
A Tor exit-node match on the connecting IP surfaces as network.tor. Separately,
the Tor Browser itself is recognized client-side by its anti-fingerprinting
configuration, independently of the network path — see
Smart Signals.
City-level geolocation is resolved server-side from the connecting IP (MaxMind)
and delivered on the webhook geo object.
| Field | Type | Description | Plan |
|---|---|---|---|
geo.country | string | ISO 3166-1 alpha-2 country code | All |
geo.city | string | City name | All |
geo.lat | number | Approximate latitude | All |
geo.lon | number | Approximate longitude | All |
geo.timezone | string | IANA timezone identifier | All |
{ "geo": { "country": "CZ", "city": "Prague", "lat": 50.05, "lon": 14.4, "timezone": "Europe/Prague" }}On Business and Enterprise plans the carrier is also identified. In the webhook
payload both fields live on the network object:
{ "network": { "vpn": false, "proxy": false, "tor": false, "datacenter": false, "isp": "Comcast Cable", "asn": 7922 }}On a session read from the Data API the split is slightly
different: isp sits on geo and asn on network. Same data, two surfaces —
read the field from where the surface you are using puts it.
On Pro and above the webhook carries a velocity block — how much the visitor has
been moving around recently:
| Field | Type | Description |
|---|---|---|
velocity.events5m | number | Events from this visitor in the last 5 minutes |
velocity.uniqueIps | number | Distinct IPs seen for this visitor |
velocity.uniqueLocations | number | Distinct locations seen for this visitor |
The block is present when the visitor counters are available at event time
(normally the primary phase). A missing block means "no data", not zeros —
don't treat its absence as "no unusual activity".
IP-blocklist matching is not part of the public contract: neither the webhook
payload nor the dashboard exposes blocklist categories. The webhook surface for
network risk is the boolean network flags, proxyDetected, the realIp object
on Business, the connection type, and the velocity counters above — use those together with the decision and bot fields (see
Smart Signals) for risk decisions.
This reads the webhook payload's network object directly. See
Webhooks for the full payload and signature verification.
// `payload` is the webhook delivery body (/docs/webhooks)function assessIPRisk(payload: WebhookPayload) { const { vpn, proxy, tor, datacenter } = payload.network
const risks: string[] = [] let riskLevel = "low"
if (tor) { risks.push("Tor exit node") riskLevel = "critical" }
if (vpn) { risks.push("VPN detected") riskLevel = riskLevel === "low" ? "medium" : riskLevel }
if (proxy) { risks.push("Proxy detected") riskLevel = riskLevel === "low" ? "medium" : riskLevel }
if (datacenter) { risks.push("Datacenter IP") riskLevel = riskLevel === "low" ? "medium" : riskLevel }
return { riskLevel, risks }}