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.

  1. Sign in at https://ipgeolocation.io.
  2. Add your origin, for example https://app.example.com .
  3. 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:

Response Preview
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.

FieldRangeMeaning
is_anonymous booleanThe 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 100How 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 100Chance that the connection runs through a proxy, including residential and rotating proxies.
vpn_score 0 to 100Chance 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.

ConfidenceWhat it meansRecommended action
Below 30Very 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 50Uncertain. 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 80Likely 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 aboveConfidently 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

Response Preview
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}
BlockKey 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.

Response Preview
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}
FieldWhy 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:

  • location says Frankfurt, Germany while asn.country is LV and 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 says is_vpn: true with is_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.


Frequently Asked Questions

No. Authorization is by origin, so there is no secret to leak.
Not this script, it is browser only. Use the IP Security API instead.
Once per meaningful action, not once per page view.
Yes, and that is the main reason it exists. Blocklists miss them because the IPs belong to ordinary consumer ISPs, while live analysis catches the anonymization itself.
No, it is certainty about is_anonymous. Risk lives in proxy_score, vpn_score and threat_score.
No. They are independent likelihoods, so both can be high, and a 100 in one does not force a 0 in the other.
The live scores describe how the connection behaves right now, while is_vpn and vpn_provider_names name the service that owns the endpoint. One common cause is a browser VPN extension, which is a proxy at the transport level, but the same reading can come from a desktop or CLI client or a residential proxy. See the note in section 5.
confidence is certainty about the live is_anonymous verdict. vpn_confidence_score and proxy_confidence_score are how sure the IP reputation database is about its own VPN or proxy classification.
Usually not. Many are ordinary privacy conscious or corporate users, which is what the bands in section 4 are for.
ip_security.security.is_tor flags known Tor exit nodes when includeIPSecurity is true.
It is lightweight and promise based. Load it with async and never await a verdict during your initial render.
Client side code can always be tampered with, which is why the result should feed a server side decision alongside your own signals rather than being the decision.

Related Links

Subscribe to Our Newsletter

Get the latest in geolocation tech, straight to your inbox.