Real-Time Proxy & VPN Detection


Overview

Detect VPNs, proxies and residential proxies live in the browser, at the moment a user acts. Your backend can fetch each result by its session_id. See what the service does and try it on the Real-Time Proxy & VPN Detection product page.


Setup

The script carries no API key. Requests are authorized by origin, so register your domain before your first call.

  1. Sign up for your IPGeolocation.io account.
  2. Add your origin as a Request 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>
    Analysis
        .startMonitoring()
        .get()
        .then(result => console.log(result))
        .catch(error => console.error(error instanceof Error ? error.message : String(error)));
</script>

Response for a clean connection:

Response Preview
1{
2  "public_ip": "119.156.x.x",
3  "public_ip_country_code": "PK",
4  "live_vpn_proxy_detection": {
5    "is_anonymous": false,
6    "confidence_score": 100,
7    "proxy_score": 0,
8    "vpn_score": 0
9  },
10  "visitor_actual_location": {
11    "actual_ip": "119.156.x.x",
12    "actual_country_code": "PK",
13    "confidence_score": 100
14  },
15  "session_id": "e3a17c5b8f2d4a6b9c0e1f7d2b5a8c4e"
16}

In plain words: this connection is not anonymized, the service is certain about it, and the visitor is where the request says they are. The session_id identifies this check; your backend uses it to fetch the result from our API.


1. Optional objects

Pass an include string to startMonitoring() to attach extra data about the public IP. Separate several names with commas, or pass all for everything.

ValueAdds
securityip_security
locationip_location
asnip_asn
companyip_company
allAll four objects
const include = "security,location";

Analysis
    .startMonitoring(include)
    .get()
    .then(result => console.log(result));

Each extra object costs credits; see Credits.


Response fields


1. The IP address the request comes from

Two top-level fields, describing the IP address the user appears to be on:

FieldTypeMeaning
public_ipstringThe IP address your server sees on the request. Behind a VPN or proxy this is the exit node's IP address, not the user's.
public_ip_country_codestringTwo-letter ISO country code for that IP address. Behind a VPN or proxy, this is the exit node's country, not the user's.

2. The live verdict: live_vpn_proxy_detection

Four fields, meant to be read together:

FieldTypeMeaning
is_anonymousbooleanThe headline answer. true means the connection is anonymized through a VPN or a proxy. It does not say which; the two scores below do.
confidence_scoreup to 100How much to trust that yes or no. 100 with is_anonymous: false means confidently clean; a low value is weak evidence either way.
proxy_score0 to 100Looks like a proxy, including residential and rotating proxies.
vpn_score0 to 100Looks like a VPN.

3. Where the visitor really is: visitor_actual_location

Where the user actually is, recovered by the live tests:

FieldTypeMeaning
actual_ipstringThe visitor's real IP address behind the VPN or proxy. Falls back to public_ip when nothing is hidden, or when the real IP cannot be detected.
actual_country_codestringTwo-letter ISO country code of the visitor's actual location.
confidence_scoreup to 100Certainty about that actual country. Gate on it before acting on a country mismatch.

4. The check itself: session_id

Top-level fields that identify the check. The browser result carries session_id; when your backend fetches the session, the response adds origin and session_ts:

FieldTypeReturned inMeaning
session_idstringBrowser and backendUnique ID of the check. Use it to fetch the result from your backend.
originstringBackend onlyOrigin of the page that ran the check, for example https://app.example.com. Confirm it matches your own domain.
session_tsnumberBackend onlyTime of the session, as Unix epoch time in milliseconds (UTC).

5. Optional objects about the public IP

Each appears only when its name, or all, is in include. They describe the public IP, so behind a VPN or proxy they refer to the exit node, not the visitor.

Objectinclude valueWhat it tells youField reference
ip_securitysecurityWhat is already known about the IP: VPN, proxy, Tor, bot, and threat signalsIP Security API response reference
ip_locationlocationWhere the IP is: country, state, city, coordinatesLocation object reference
ip_asnasnWho routes the IP: AS number, organization, typeASN object reference
ip_companycompanyWho owns the IP: company name, type, domainCompany object reference

Response with include: "all":

Response Preview
1{
2  "public_ip": "94.237.x.x",
3  "public_ip_country_code": "DE",
4  "live_vpn_proxy_detection": {
5    "is_anonymous": true,
6    "confidence_score": 90,
7    "proxy_score": 90,
8    "vpn_score": 10
9  },
10  "visitor_actual_location": {
11    "actual_ip": "119.156.x.x",
12    "actual_country_code": "PK",
13    "confidence_score": 80
14  },
15  "ip_security": {
16    "threat_score": 50,
17    "is_tor": false,
18    "is_proxy": true,
19    "proxy_provider_names": [],
20    "proxy_confidence_score": 99,
21    "proxy_last_seen": "",
22    "is_residential_proxy": false,
23    "is_vpn": true,
24    "vpn_provider_names": ["Browsec VPN"],
25    "vpn_confidence_score": 99,
26    "vpn_last_seen": "2026-08-27",
27    "is_relay": false,
28    "relay_provider_name": "",
29    "is_anonymous": true,
30    "is_known_attacker": false,
31    "is_bot": false,
32    "bot_confidence_score": 0,
33    "bot_operator_name": "",
34    "bot_type": "",
35    "is_known_good_bot": false,
36    "bot_last_seen": "",
37    "is_spam": false,
38    "is_cloud_provider": true,
39    "cloud_provider_name": "UpCloud Ltd",
40    "is_corporate_gateway": false,
41    "corporate_gateway_type": "",
42    "corporate_gateway_provider_name": ""
43  },
44  "ip_location": {
45    "continent_code": "EU",
46    "continent_name": "Europe",
47    "country_code2": "DE",
48    "country_code3": "DEU",
49    "country_name": "Germany",
50    "country_name_official": "Federal Republic of Germany",
51    "country_capital": "Berlin",
52    "state_prov": "Hesse",
53    "state_code": "DE-HE",
54    "district": "Frankfurt",
55    "city": "Frankfurt",
56    "locality": "Frankfurt",
57    "accuracy_radius": "8.588",
58    "confidence": "medium",
59    "dma_code": "",
60    "zipcode": "60311",
61    "latitude": "50.11208",
62    "longitude": "8.68341",
63    "is_eu": true,
64    "country_flag": "https://ipgeolocation.io/static/flags/de_64.png",
65    "geoname_id": "6463469",
66    "country_emoji": "🇩🇪"
67  },
68  "ip_asn": {
69    "as_number": "AS202053",
70    "organization": "UpCloud Ltd",
71    "country": "FI",
72    "type": "BUSINESS",
73    "domain": "upcloud.com",
74    "date_allocated": "2014-04-24",
75    "rir": "RIPE"
76  },
77  "ip_company": {
78    "name": "UpCloud Cloud Servers",
79    "type": "HOSTING",
80    "domain": "upcloud.com"
81  },
82  "session_id": "4f1c2a7e9d0b4e3a8c6f5b2d1e7a9c30"
83}

Reading it in code:

const liveDetection  = result.live_vpn_proxy_detection;
const actualLocation = result.visitor_actual_location;
const ipSecurity     = result.ip_security;

liveDetection.confidence_score;                                        // 90
actualLocation.actual_country_code;                                    // "PK"
actualLocation.actual_country_code !== result.public_ip_country_code;  // true: presenting DE, actually in PK
ipSecurity.vpn_provider_names;                                         // ["Browsec VPN"]
ipSecurity.threat_score;                                               // 50
result.ip_location.city;                                               // "Frankfurt": the exit node, not the visitor
result.ip_asn.organization;                                            // "UpCloud Ltd"
result.ip_company.type;                                                // "HOSTING"
result.session_id;                                                     // "4f1c2a7e9d0b4e3a8c6f5b2d1e7a9c30": send this to your backend

Fetching results from your backend

Send the session_id from the browser result to your backend, then fetch the session from our API:

GET https://api.ipgeolocation.io/session?sessionId=SESSION_ID

Authenticate with apiKey=YOUR_API_KEY in the query string, or with an Origin header set to a domain registered as a Request Origin in your account.


1. With an API key

curl 'https://api.ipgeolocation.io/session?sessionId=SESSION_ID&apiKey=YOUR_API_KEY'

2. With Request Origin

curl 'https://api.ipgeolocation.io/session?sessionId=SESSION_ID' \
  --header 'Origin: https://app.example.com'

3. Response

The response is the check's result plus origin and session_ts:

Response Preview
1{
2  "public_ip": "203.0.x.x",
3  "public_ip_country_code": "PK",
4  "live_vpn_proxy_detection": {
5    "is_anonymous": false,
6    "confidence_score": 100,
7    "proxy_score": 0,
8    "vpn_score": 0
9  },
10  "visitor_actual_location": {
11    "actual_ip": "203.0.x.x",
12    "actual_country_code": "PK",
13    "confidence_score": 100
14  },
15  "session_id": "9b49ebc2393e4f7d900938d5496c0963",
16  "origin": "https://app.example.com",
17  "session_ts": 1793304897382
18}

Errors carry a message: 400 for a missing or malformed sessionId, 401 for an invalid API key, and 404 when the session has expired or belongs to another account.


Acting on the verdict

When is_anonymous is true, confidence_score decides how much friction the session deserves. Blocking everything the service flags costs real customers; ignoring the flag defeats the check. Work in bands, and tune the cutoffs against your own confirmed fraud outcomes:

confidence_scoreWhat it meansRecommended action
Below 30Very weak signal. Expect false positives.Allow. Log the result, add nothing to the user's path.
30 to 50Uncertain. Could be an unusual network, a mobile carrier, or a corporate gateway.Allow and monitor. Feed it into your wider risk score; add friction only if another signal agrees.
51 to 80Likely anonymized. Enough for friction, not for a hard denial alone.Challenge. MFA, email or SMS verification, or a CAPTCHA. Hold payouts and first orders for review.
81 and aboveConfidently anonymized.Block or restrict. High proxy_score: block or hold for review. High vpn_score: prefer a hard challenge, since many ordinary people browse through VPNs.

These bands apply only when is_anonymous is true. High confidence with is_anonymous: false is a clean visitor, not a risky one.


Credits

includeCredits per call
Default3
security+2
Any or all of location, asn, company+1
all6

The three objects location, asn, and company share a single credit, whether you include one or all of them. For example, security alone costs 5, location,asn,company costs 4, and security,location costs 6.

Fetching a session from your backend costs no extra credits. Credits are charged once, for the check itself.


FAQs

No. Authorization is by Request Origin: register your domain once and requests from it and its subdomains authenticate automatically. No secret ships to the browser. If your backend fetches sessions with an API key, that key stays on your server.
The check itself runs only in a browser. Your backend can then fetch its result by session_id; see Fetching results from your backend. For backend-only, mobile, or bulk checks with no browser involved, use the IP Security API, which returns only the reputation data through a key-authenticated API call.
For 30 minutes after it is created. After that, it can no longer be retrieved, and the session endpoint returns 404.
Usually 1 to 5 seconds, because live network tests run against the connection rather than a single database lookup. Start monitoring on page load so the wait is over before the user reaches your decision point.
Yes. visitor_actual_location returns the actual_ip and actual_country_code recovered by the live tests, with its own confidence_score. Use that country for fraud and compliance rules, and gate on the confidence before acting on a mismatch.
Yes, and that is the main reason it exists. Static lists miss them because the IPs come from constantly refreshed pools of ordinary consumer ISP addresses with no listing history yet; live analysis catches the anonymization behavior itself.
ip_security.is_vpn and is_proxy come from our IP intelligence database, updated daily: they tell you an IP address is known to be associated with VPN or proxy infrastructure. The live vpn_score and proxy_score come from inspecting the current connection, so they describe this session rather than the address's history.
No. It classifies a session with live connection-level techniques only, and does not consult the IP Security database or traditional IP intelligence to reach its verdict. Including security simply attaches the database view next to the live one.
A residential IP is a real household address that may also be part of a proxy network. The homeowner's own request and an anonymous request routed through the network can arrive from it seconds apart. A database can say the address is associated with residential proxy infrastructure, but not how it is being used on this connection, so acting on the association alone can affect legitimate users too.
Yes. No offline database has complete coverage: new VPN endpoints, private proxies, and rotating residential pools may not be listed yet. Real-time detection does not depend on prior knowledge of the IP address.
In our testing, classification accuracy is around 98%. In the remaining cases, confidence_score was below 35, so gating on confidence gives you an extra signal on the sessions most likely to be misread.
Both, where you can. The IP Security API gives broader IP-level context and known associations; real-time detection tells you what the connection is doing right now. Together they support better decisions without unnecessarily blocking legitimate users.
No. It measures certainty about the verdict it sits next to, in either direction: 100 with is_anonymous: false means confidently clean. Risk lives in proxy_score, vpn_score, and ip_security.threat_score.
Usually not. Many are ordinary privacy-conscious or corporate users. Work the confidence bands: log the weak signals, challenge the likely ones, and reserve blocks for high confidence backed by a second signal.
3 credits per call, 5 with security included, and 1 more for any of location, asn, or company, so all costs 6. Fetching the result from your backend by session_id costs nothing extra. Credits come from your plan's normal credit pool. It is a paid plan feature; contact our support team for a free trial.
Real-Time Proxy &VPN Detection

Looking for features, modules, and use cases? Visit the Real-Time Proxy & VPN Detection page.

Explore Real-Time Proxy & VPN Detection

Subscribe to Our Newsletter

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