[{"data":1,"prerenderedAt":143},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fbot-and-vpn-detection":3},{"page":4,"toc":135,"updated":142},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fbot-and-vpn-detection","Bot, VPN and Tor detection","Bot Detection and VPN Detection API: Fields and Signals","Detect bots, VPNs, proxies and Tor with one identification: which fields on the event, signal groups and webhooks tell you, and how to act on them.",[10,13,15,20,47,50,80,82,85,93,95,98,100,103,105,109,112],{"type":11,"text":12},"p","Every Fingerly identification doubles as a **bot detection API** and a **VPN detection API**. The same call that returns the visitor ID also reports whether the visitor looks automated and whether the connection comes through Tor, a VPN, a proxy or a hosting provider. You read the answer from your backend, on the stored event or in a webhook.",{"type":11,"text":14},"These are signals for your own policy, not verdicts. Fingerly never blocks anyone on its own, and a VPN is not evidence of fraud by itself, which is why you set what each one is worth.",{"type":16,"level":17,"text":18,"id":19},"heading",2,"Where the answer is","where-the-answer-is",{"type":21,"columns":22,"rows":26},"table",[23,24,25],"What you want to know","Where to read it","Values",[27,31,35,39,43],[28,29,30],"Is the visitor a person, an automation or a crawler?","`visitor_kind` on the [event](\u002Freference\u002Fget-event#response)","`human`, `ai_bot`, `automation` or `search_crawler`",[32,33,34],"Does the connection come through an anonymising network?","`anonymity_network` on the event","`tor`, `vpn`, `proxy` or `hosting`. Omitted for an ordinary connection.",[36,37,38],"Whose network is it?","`asn`, `asn_name` and `country_code` on the event","The autonomous system number, its name and the ISO country",[40,41,42],"Which signals raised the score?","`triggers` on the event","The [signal groups](\u002Fdocs\u002Fsignals) that fired, heaviest first, each with `weight` and `confidence`",[44,45,46],"How suspicious is the session overall?","`suspect_score` and `suspect_level`","A [score](\u002Fdocs\u002Fsuspect-score), and `low`, `medium` or `high`",{"type":16,"level":17,"text":48,"id":49},"Signal groups for bots and anonymised networks","signal-groups-for-bots-and-anonymised-networks",{"type":21,"columns":51,"rows":55},[52,53,54],"Group","Key in `triggers`","Meaning",[56,60,64,68,72,76],[57,58,59],"Bot","`bot`","The client is automated, or says it is a bot.",[61,62,63],"Tor exit node","`tor`","The connection arrives from the Tor network.",[65,66,67],"VPN","`vpn`","The connection arrives over a VPN.",[69,70,71],"Datacenter proxy","`datacenter_proxy`","The address belongs to a proxy or a hosting provider rather than a consumer connection.",[73,74,75],"Residential proxy","`residential_proxy`","Traffic is relayed through someone else's home connection.",[77,78,79],"IP reputation","`ip_reputation`","The address has a recent history of abuse.",{"type":11,"text":81},"Each group has a default weight per platform, listed in the [signals reference](\u002Fdocs\u002Fsignals), and you can change them with [risk weights](\u002Fdocs\u002Frisk-weights). VPN and residential proxy findings can be weighed by how they were recognised or by confidence. The `bot` group applies to the web. Whether the visitor is a crawler or an AI agent is told by `visitor_kind`, not by a signal group.",{"type":16,"level":17,"text":83,"id":84},"Read it on your server","read-it-on-your-server",{"type":86,"samples":87},"code",[88],{"label":89,"lang":90,"code":91,"check":92},"Node.js","ts","const event = await fingerly.events.get(requestId)\n\nif (event.visitor_kind === 'automation') return refuse()\nif (event.anonymity_network === 'tor') return challenge()\nif (event.anonymity_network === 'vpn' && event.suspect_level !== 'low') return challenge()",false,{"type":11,"text":94},"An event that is not recorded yet comes back as pending, and an identification that could not be scored has no score, which is not a score of `0`. See [server-side verification](\u002Fdocs\u002Fserver-side-verification) before you act on a result.",{"type":16,"level":17,"text":96,"id":97},"Get it pushed to you","get-it-pushed-to-you",{"type":11,"text":99},"[`identification.completed`](\u002Freference\u002Fwebhooks\u002Fidentification-completed) is sent for every identification. [`visitor.suspect`](\u002Freference\u002Fwebhooks\u002Fvisitor-suspect) is sent when one reaches the `high` level, with every signal that fired in `triggers`, each carrying its `signal`, its `group`, its `weight` and its `confidence`. Subscribe to it to react to automation or Tor traffic without polling. See [webhooks](\u002Fdocs\u002Fwebhooks).",{"type":16,"level":17,"text":101,"id":102},"What a browser or an app is told","what-a-browser-or-an-app-is-told",{"type":11,"text":104},"A page or an app can read `suspect_score` and `suspect_level` by default, never the signals, and not at all if you [hide them on the key](\u002Fdocs\u002Fapi-keys#hide-the-score). The groups and fields above are told only to your backend and the dashboard, because knowing which check tripped teaches a fraudster what to hide.",{"type":106,"tone":107,"text":108},"callout","warning","Do not decide in the browser. Read the event with a secret key on your server, and check it belongs to the action you are protecting.",{"type":16,"level":17,"text":110,"id":111},"Recipes","recipes",{"type":113,"columns":17,"cards":114},"cards",[115,120,125,130],{"title":116,"text":117,"href":118,"icon":119},"Credential stuffing","Refuse automation and count failed logins per device.","\u002Fdocs\u002Fuse-cases\u002Fcredential-stuffing","bot",{"title":121,"text":122,"href":123,"icon":124},"Sign-up abuse","Send automated and farmed sign-ups to review.","\u002Fdocs\u002Fuse-cases\u002Fsign-up-abuse","users",{"title":126,"text":127,"href":128,"icon":129},"Signals reference","Every signal group and its default weight.","\u002Fdocs\u002Fsignals","list",{"title":131,"text":132,"href":133,"icon":134},"Risk weights","Change what each signal is worth.","\u002Fdocs\u002Frisk-weights","sliders",[136,137,138,139,140,141],{"id":19,"text":18,"level":17},{"id":49,"text":48,"level":17},{"id":84,"text":83,"level":17},{"id":97,"text":96,"level":17},{"id":102,"text":101,"level":17},{"id":111,"text":110,"level":17},"2026-11-12T11:59:51.000Z",1794493376647]