Real-Time Proxy and VPN Detection
Overview
Detect VPNs, proxies and residential proxies live in the browser, at the moment a user acts.
Product page: https://ipgeolocation.io/real-time-proxy-and-vpn-detection.html
Setup
The script runs in the browser and carries no API key. Requests are authorized by origin instead, so you have to register the domain you will call from as a Request Origin in your IPGeolocation dashboard before your first call works.
- Sign in at https://ipgeolocation.io.
- Add your origin, for example
https://app.example.com. - Save.
Quick start
<script src="https://static.ipgeolocation.io/web-assets/static/security/session-analysis.js"></script>
<script>
const includeIPSecurity = false;
Analysis
.startMonitoring(includeIPSecurity)
.get()
.then(result => console.log(result))
.catch(error => console.error(error instanceof Error ? error.message : String(error)));
</script>Response:
1{
2 "live_vpn_proxy_detection": {
3 "is_anonymous": false,
4 "confidence": 100,
5 "proxy_score": 0,
6 "vpn_score": 0
7 }
8}In plain words: this connection is not anonymized, and we are certain about it.
live_vpn_proxy_detection
Always present. Four fields, and they are meant to be read together.
| Field | Range | Meaning |
|---|---|---|
is_anonymous | boolean | The headline answer. true means the connection is anonymized, either through a VPN or through a proxy. It does not tell you which of the two. |
confidence | up to 100 | How certain the service is about the is_anonymous value above. This is not a risk score. confidence: 100 with is_anonymous: false means "confidently clean". A low value means the verdict is weak evidence either way. |
proxy_score | 0 to 100 | Chance that the connection runs through a proxy, including residential and rotating proxies. |
vpn_score | 0 to 100 | Chance that the connection runs through a VPN. |
The mental model: is_anonymous tells you whether the connection is hiding, confidence tells you how much to trust that yes or no, and proxy_score / vpn_score tell you what kind of hiding it looks like.
The two scores are independent likelihoods, not two halves of a split, so they do not add up to 100.
What to do at each confidence level
This is the part worth getting right. When is_anonymous is true , confidence decides how much friction the user deserves. Blocking everything the service flags will cost you real customers, and ignoring the flag defeats the point of running the check, so work in bands.
| Confidence | What it means | Recommended action |
|---|---|---|
| Below 30 | Very weak signal. Expect false positives in this band. | Allow. Log the result for later analysis, add nothing to the user's path. Do not challenge on this alone. |
| 30 to 50 | Uncertain. Could easily be an unusual network, a mobile carrier or a corporate gateway. | Allow and monitor. Let the action through, tag the session as slightly elevated risk, feed it into your wider risk score. Add friction only if another signal agrees, such as a brand new account or a mismatched billing country. |
| 51 to 80 | Likely anonymized. Enough for friction, not enough for a hard denial on its own. | Challenge. MFA, email or SMS verification, or a CAPTCHA. On payouts and first orders, hold for manual review instead of denying outright. |
| 81 and above | Confidently anonymized. | Block or restrict, with the score type deciding how hard. High proxy_score usually means abuse: block, or hold the order and send it to review. High vpn_score deserves a hard challenge instead, since plenty of ordinary people browse through a VPN. Two caveats: a high proxy_score next to a named consumer VPN in vpn_provider_names is not automatically abuse, since it can come from a browser extension, a desktop or CLI client, or a residential proxy (see section 5), and an outright block is safest when a second signal backs it up, such as threat_score >= 70 , is_known_attacker or is_bot . |
Tune these numbers against your own confirmed fraud data, because the right cutoff depends on your traffic and on how much a false positive costs you.
Full response when includeIPSecurity is true
Setting the flag to true enriches the live verdict with an ip_security object, resolved from our IP intelligence database in the same round trip. That lookup supplies the reputation and risk signals for the address, together with contextual geolocation, network, ASN, company, currency and timezone data. The two halves answer different questions: live_vpn_proxy_detection describes how the connection behaves right now, while ip_security describes what is already known about the address it arrives from.
1. Response shape
1{
2 "live_vpn_proxy_detection": {
3 "is_anonymous": true,
4 "confidence": 100,
5 "proxy_score": 100,
6 "vpn_score": 30
7 },
8 "ip_security": {
9 "ip": "37.203.37.112",
10 "location": {
11 "continent_code": "EU",
12 "continent_name": "Europe",
13 "country_code2": "DE",
14 "country_code3": "DEU",
15 "country_name": "Germany",
16 "country_name_official": "Federal Republic of Germany",
17 "country_capital": "Berlin",
18 "state_prov": "Hesse",
19 "state_code": "DE-HE",
20 "district": "Frankfurt",
21 "city": "Frankfurt",
22 "zipcode": "60311",
23 "latitude": "50.11090",
24 "longitude": "8.68210",
25 "is_eu": true,
26 "country_flag": "https://ipgeolocation.io/static/flags/de_64.png",
27 "geoname_id": "12218247",
28 "country_emoji": "🇩🇪"
29 },
30 "country_metadata": {
31 "calling_code": "+49",
32 "tld": ".de",
33 "languages": ["de"]
34 },
35 "network": {
36 "connection_type": "",
37 "route": "37.203.37.0/24",
38 "is_anycast": false
39 },
40 "currency": {
41 "code": "EUR",
42 "name": "Euro",
43 "symbol": "€"
44 },
45 "asn": {
46 "as_number": "AS215373",
47 "organization": "Sabiedriba ar ierobezotu atbildibu DELSKA Latvia",
48 "country": "LV",
49 "type": "ISP",
50 "domain": "delska.com",
51 "date_allocated": "2025-09-10",
52 "rir": "RIPE"
53 },
54 "company": {
55 "name": "SIA Digitalas Ekonomikas Attistibas Centrs",
56 "type": "GOVERNMENT",
57 "domain": "deac.eu"
58 },
59 "security": {
60 "threat_score": 90,
61 "is_tor": false,
62 "is_proxy": false,
63 "proxy_provider_names": [],
64 "proxy_confidence_score": 0,
65 "proxy_last_seen": "",
66 "is_residential_proxy": false,
67 "is_vpn": true,
68 "vpn_provider_names": ["Browsec VPN"],
69 "vpn_confidence_score": 80,
70 "vpn_last_seen": "2026-06-25",
71 "is_relay": false,
72 "relay_provider_name": "",
73 "is_anonymous": true,
74 "is_known_attacker": true,
75 "is_bot": false,
76 "is_spam": true,
77 "is_cloud_provider": true,
78 "cloud_provider_name": "SIA Digitalas Ekonomikas Attistibas Centrs"
79 },
80 "time_zone": {
81 "name": "Europe/Berlin",
82 "offset": 1,
83 "offset_with_dst": 2,
84 "current_time": "2026-08-03 13:49:56.890+0200",
85 "current_time_unix": 1785757796.89,
86 "current_tz_abbreviation": "CEST",
87 "current_tz_full_name": "Central European Summer Time",
88 "standard_tz_abbreviation": "CET",
89 "standard_tz_full_name": "Central European Standard Time",
90 "is_dst": true,
91 "dst_savings": 1,
92 "dst_exists": true,
93 "dst_tz_abbreviation": "CEST",
94 "dst_tz_full_name": "Central European Summer Time",
95 "dst_start": {
96 "utc_time": "2026-03-29 TIME 01:00",
97 "duration": "+1.00H",
98 "gap": true,
99 "date_time_after": "2026-03-29 TIME 03:00",
100 "date_time_before": "2026-03-29 TIME 02:00",
101 "overlap": false
102 },
103 "dst_end": {
104 "utc_time": "2026-10-25 TIME 01:00",
105 "duration": "-1.00H",
106 "gap": false,
107 "date_time_after": "2026-10-25 TIME 02:00",
108 "date_time_before": "2026-10-25 TIME 03:00",
109 "overlap": true
110 }
111 }
112 }
113}| Block | Key fields |
|---|---|
location | Country, state, city, zip, latitude, longitude, is_eu , flag URL, emoji |
country_metadata | calling_code , tld , languages |
network | connection_type , route , is_anycast |
currency | code , name , symbol |
asn | as_number , organization , country , type , domain , date_allocated , rir |
company | name , type , domain |
security | The risk block. See security is the block that matters. |
time_zone | name , offset , current_time , DST fields |
2. security is the block that matters
It holds the threat score, the anonymizer flags and the name of the VPN or proxy provider. Everything else in ip_security is context around it, so if you read one block, read this one.
1"security": {
2 "threat_score": 90,
3 "is_tor": false,
4 "is_proxy": false,
5 "proxy_provider_names": [],
6 "proxy_confidence_score": 0,
7 "proxy_last_seen": "",
8 "is_residential_proxy": false,
9 "is_vpn": true,
10 "vpn_provider_names": ["Browsec VPN"],
11 "vpn_confidence_score": 80,
12 "vpn_last_seen": "2026-06-25",
13 "is_relay": false,
14 "relay_provider_name": "",
15 "is_anonymous": true,
16 "is_known_attacker": true,
17 "is_bot": false,
18 "is_spam": true,
19 "is_cloud_provider": true,
20 "cloud_provider_name": "SIA Digitalas Ekonomikas Attistibas Centrs"
21}| Field | Why it matters |
|---|---|
threat_score (0 to 100) | The most useful single number in the response. Treat >= 70 as strong corroboration for any anonymization verdict. |
is_vpn , is_proxy , is_residential_proxy , is_relay , is_tor | The anonymizer type, as five separate booleans instead of one label. This is how you split VPN handling from proxy handling, and is_residential_proxy is the one most worth its own rule, since residential proxies correlate with abuse far more than a commercial VPN does. |
vpn_provider_names , proxy_provider_names | Arrays of named services, for example ["Browsec VPN"] . Lets you allow a corporate VPN you recognize, block a provider you keep seeing in confirmed fraud, and tell a support agent exactly what the user is on. Empty when nothing is identified. |
vpn_confidence_score , proxy_confidence_score | How sure the database is about that VPN or proxy classification, 0 to 100. Separate from the live confidence field in live_vpn_proxy_detection , which is about the live verdict. |
vpn_last_seen , proxy_last_seen | Date the IP was last observed acting as a VPN or proxy, for example 2026-06-25 . A recent date makes the listing stronger evidence, an old one makes it weaker. Empty when never seen. |
relay_provider_name | The named relay service when is_relay is true . Empty otherwise. |
is_known_attacker , is_spam , is_bot | Hard evidence of past abuse. is_known_attacker is strong enough to act on by itself. |
is_cloud_provider , cloud_provider_name | A hosting network rather than a consumer ISP. Very common for VPN exit nodes, and a poor fit for a genuine retail customer. |
is_anonymous | The database view of the IP. |
const sec = result.ip_security?.security;
sec.threat_score; // 90
sec.is_vpn; // true
sec.vpn_provider_names; // ["Browsec VPN"]
sec.vpn_confidence_score; // 80
sec.vpn_last_seen; // "2026-06-25"There are two is_anonymous fields, and the pair is useful. live_vpn_proxy_detection.is_anonymous is the live view. ip_security.security.is_anonymous is the IP reputation database view. Both true gives you strong corroboration. Database only means the IP is listed but may not be anonymizing right now. Live only means anonymization no blocklist has caught yet, which is the residential proxy case and the whole reason this product exists.
3. Reading that example
Almost every signal points the same way, which makes it a good case to walk through.
confidence: 100 puts it in the 81 and above band immediately. The security block is what turns that into a block rather than a challenge: threat_score: 90 , a known attacker, spam history, a hosting network, and a named VPN endpoint (Browsec VPN, vpn_confidence_score: 80 , last seen 2026-06-25 ).
Two details worth noticing:
-
locationsays Frankfurt, Germany whileasn.countryisLVand the AS belongs to a Latvian hosting company. A hosting ASN in one country geolocating to a datacenter city in another is a classic VPN exit node fingerprint, so do not treat Germany as where this user actually is. - The live scores and the database disagree on the type. Live says
proxy_score: 100, the database saysis_vpn: truewithis_proxy: false. That is not a contradiction, and you take the higher risk reading of the two.
Verdict: block, and log the payload so you can point at the specific reason if the user appeals.
Signals that should push a decision harder: threat_score >= 70 escalates one band. is_known_attacker , is_spam or is_bot escalates to block or hard review. is_cloud_provider: true on a consumer flow means automation, so rate limit or challenge regardless of confidence. An IP country that does not match the billing country escalates one band on payment flows. One signal alone rarely justifies a block, but two or three pointing the same way usually do.
Do and don't
Do: run it on high value actions (signup, login, checkout, payout, promo redemption, referral claims), start monitoring on load and call .get() at the decision point, always gate on confidence , treat VPN and proxy as different risks, store payloads so you can tune your thresholds against real outcomes, and prefer a challenge over a hard block.
Don't: call it on every page view, hard block every VPN user (that burns real revenue), trust the client payload as your only authority, act on low confidence verdicts, or let a detection error break the user's flow.
Privacy: mention the fraud prevention processing in your privacy policy and consent notices, note it in your GDPR and CCPA records of processing, apply a retention limit to stored payloads, and give users a human review path for automated declines where the law requires one.