Residential proxy detection is one of the most challenging problems in IP-based threat intelligence. VPNs run on known datacenter IP ranges; you can blocklist them. Tor exit nodes publish their addresses. Datacenter proxies sit on hosting ASNs that reputation databases flag in hours. Residential proxies do none of that. They route traffic through real consumer ISP connections (Comcast, Vodafone, Jio, Deutsche Telekom), making every request look like it comes from someone's living room.
The FBI published a PSA in March 2026 warning that residential proxies have become a standard tool for credential stuffing, account takeovers, and purchase fraud. Google's Threat Intelligence team disrupted what they called the largest residential proxy network a month earlier. The threat is not emerging. It's operational.
This tutorial walks through detecting residential proxy traffic using an IP security API, with production code in cURL, Python, and Node.js.
TL;DR
- Residential proxies use real ISP-assigned IPs, so VPN blocklists and datacenter ASN checks miss them.
- API-based detection matches IPs against known residential proxy networks and returns provider attribution, confidence scores, and a composite threat score.
- The examples below use a dedicated security endpoint that returns 17 fields per IP, including
is_residential_proxywith provider names. - Use confidence scores and threat scores to add friction rather than hard-blocking. The same IP may serve both proxy traffic and legitimate users.
Why residential proxies bypass traditional detection
VPN detection is a solved problem (mostly). Commercial VPN providers run dedicated server infrastructure on hosting ASNs. NordVPN, ExpressVPN, Mullvad all operate on IP ranges that every major reputation database can flag within days.
Residential proxies work differently. They source IPs through three channels:
Embedded SDKs. Proxy providers pay mobile app developers to bundle their SDK. When someone installs the app, their device becomes a proxy exit node. The FBI PSA specifically calls this out as the most common sourcing method.
Peer-to-peer bandwidth sharing. "Earn money from your internet connection" apps funnel traffic through consumer devices. Users opt in without understanding their connection will be used for credential stuffing or ad fraud.
Compromised devices. Malware turns IoT devices, routers, and set-top boxes into proxy nodes. Trend Micro documented residential proxy providers in eastern Asia shipping pre-infected Android devices with proxy SDKs installed at the factory.
The result: proxy traffic originates from IPs assigned to Comcast, Jio, Telekom, and other consumer ISPs. To a traditional IP reputation system, it looks like a legitimate user.
How bad is the false negative problem? In 2024, Peakhour tested five major IP intelligence services against 25 IPs that had been actively used as residential proxies within the previous five minutes. MaxMind detected 0 out of 25. IPQualityScore detected 6 out of 25. proxycheck.io detected 0 out of 25. Cloudflare's research found that 4 out of 5 requests from residential proxy IPs are legitimate traffic from the device's real owner. That ratio is why hard-blocking creates unacceptable collateral damage.
What to look for in a detection API
Several IP intelligence APIs offer proxy detection: Spur, IPQualityScore, IPGeolocation, IPinfo, proxycheck.io, IP2Proxy, and others. Before picking one, check for:
A dedicated residential proxy flag separate from the generic is_proxy field. vpnapi.io, IPstack, and AbstractAPI return boolean proxy flags but don't distinguish residential proxies from datacenter proxies.
Provider attribution. Knowing an IP is "a proxy" is less useful than knowing it's Oxylabs, 922 Proxy, or Bright Data. Provider names let you build granular rules.
Confidence scores, not just boolean flags. A binary yes/no produces false positives on mobile carriers and large ISPs sharing IPs across thousands of users via CGNAT.
A composite threat score. An IP might simultaneously be a residential proxy, a known attacker, and a spam source. A composite score saves you from writing the aggregation logic.
Update frequency. Residential proxy IPs churn faster than VPN server IPs. A database that refreshes weekly will miss the majority of active endpoints.
I'll use ipgeolocation.io for the examples because the Security API returns a dedicated is_residential_proxy flag with provider names and confidence scores on all paid plans starting at $19/month. The same approach works with any API that provides equivalent signals.
Making the API call
The dedicated security endpoint:
curl -s "https://api.ipgeolocation.io/v3/security?apiKey=$IPGEO_API_KEY&ip=85.117.56.115" | python3 -m json.tool
{
"ip": "85.117.56.115",
"security": {
"threat_score": 75,
"is_tor": false,
"is_proxy": true,
"proxy_provider_names": [
"Evomi Proxy",
"DataImpulse",
"NetNut",
"Proxy Scrape"
],
"proxy_confidence_score": 90,
"proxy_last_seen": "2026-04-22",
"is_residential_proxy": true,
"is_vpn": true,
"vpn_provider_names": [
"Bestproxyswitcher VPN"
],
"vpn_confidence_score": 90,
"vpn_last_seen": "2026-03-19",
"is_relay": false,
"relay_provider_name": "",
"is_anonymous": true,
"is_known_attacker": true,
"is_bot": false,
"is_spam": false,
"is_cloud_provider": false,
"cloud_provider_name": ""
}
}
A few things to notice here. The is_proxy flag is true and is_residential_proxy is also true, so this IP is being used by a residential proxy network. The proxy_provider_names array identifies the specific provider (Evomi Proxy, a commercial residential proxy service). The proxy_confidence_score of 85 means the API has high confidence in this classification. And threat_score of 75 places this IP in the "high risk" range, where you'd typically add friction.
Compare that to what a VPN-only API would return: a generic boolean flag with no provider name, no confidence score, and often no residential proxy flag at all. The difference between "this IP is suspicious" and "this IP is on the Evomy Proxy residential network with 85% confidence" is the difference between a useful detection system and a noisy one.
For the full API documentation, including field-level reference tables and error codes, see the docs.
Python implementation
import os
import requests
def check_residential_proxy(ip_address):
"""Check if an IP is a residential proxy. Returns a dict with detection results."""
api_key = os.environ.get("IPGEO_API_KEY")
if not api_key:
raise EnvironmentError("IPGEO_API_KEY environment variable not set")
try:
response = requests.get(
"https://api.ipgeolocation.io/v3/security",
params={"apiKey": api_key, "ip": ip_address},
timeout=(1.0, 1.5), # connect timeout, read timeout
)
response.raise_for_status()
except requests.exceptions.Timeout:
# Fail open: if the API is unreachable, allow the request through
return {"detected": False, "reason": "api_timeout", "action": "allow"}
except requests.exceptions.RequestException as e:
return {"detected": False, "reason": f"api_error: {e}", "action": "allow"}
data = response.json()
security = data.get("security", {})
is_residential = security.get("is_residential_proxy", False)
confidence = security.get("proxy_confidence_score", 0)
threat_score = security.get("threat_score", 0)
providers = security.get("proxy_provider_names", [])
# Decision logic based on threat_score ranges
if is_residential and confidence >= 70:
if threat_score >= 80:
action = "block"
elif threat_score >= 45:
action = "challenge" # CAPTCHA, step-up auth, or rate limit
else:
action = "monitor" # log but allow
else:
action = "allow"
return {
"detected": is_residential,
"confidence": confidence,
"threat_score": threat_score,
"providers": providers,
"action": action,
}
result = check_residential_proxy("85.117.56.115")
print(f"Residential proxy: {result['detected']}")
print(f"Action: {result['action']}")
if result.get("providers"):
print(f"Provider: {', '.join(result['providers'])}")
Pitfall: The
timeout=(1.0, 1.5)matters. For an inline check on a login flow, anything slower degrades UX enough to hurt conversion. And the function fails open on API errors: if the API is unreachable, the request passes through. Failing closed means your login page breaks on every API hiccup. Fail open on detection; fail closed only on payment fraud flows where the cost of a miss exceeds the cost of a false block.
Node.js implementation
async function checkResidentialProxy(ipAddress) {
const apiKey = process.env.IPGEO_API_KEY;
if (!apiKey) {
throw new Error("IPGEO_API_KEY environment variable not set");
}
const url = `https://api.ipgeolocation.io/v3/security?apiKey=${apiKey}&ip=${ipAddress}`;
try {
const response = await fetch(url, {
signal: AbortSignal.timeout(1500), // 1.5s total timeout
});
if (!response.ok) {
console.error(`Security API returned ${response.status}`);
return { detected: false, action: "allow", reason: "api_error" };
}
const data = await response.json();
const security = data?.security ?? {};
const isResidential = security.is_residential_proxy ?? false;
const confidence = security.proxy_confidence_score ?? 0;
const threatScore = security.threat_score ?? 0;
const providers = security.proxy_provider_names ?? [];
let action = "allow";
if (isResidential && confidence >= 70) {
if (threatScore >= 80) action = "block";
else if (threatScore >= 45) action = "challenge";
else action = "monitor";
}
return { detected: isResidential, confidence, threatScore, providers, action };
} catch (err) {
// Fail open on network errors and timeouts
console.error("Security API check failed:", err.message);
return { detected: false, action: "allow", reason: "timeout_or_network" };
}
}
AbortSignal.timeout(1500) kills the request after 1.5 seconds regardless of connection state. Optional chaining on data?.security handles partial or unexpected responses.
Building decision logic
The code uses a three-tier matrix based on threat_score:
1 to 19 (low risk): Allow the request. Even if is_residential_proxy is true, a low threat score with low confidence usually means a brief or historical proxy association. Log it.
45 to 79 (elevated risk): This is where most active residential proxy traffic lands. Add friction: CAPTCHA, email verification, step-up auth. Don't hard-block; the IP might serve legitimate users behind CGNAT.
80 to 100 (high risk): Multiple strong signals stacked together. Block or require manual review.
Pitfall: Indian mobile carriers (Jio, Airtel) route millions of users through a handful of public IPs via CGNAT. A residential proxy flag on a Jio IP with confidence 30 is likely noise. The same flag on a European broadband IP with confidence 90 is almost certainly a detection. Your decision function should treat confidence below 50 differently from confidence above 70.
Caching and rate considerations
Residential proxy IPs rotate fast. A scraper using Oxylabs or Bright Data might cycle exit nodes every request. That's why detection APIs exist, but it also means aggressive caching defeats the purpose.
For auth and fraud workflows, cache detection results for 5 to 15 minutes. For content personalization, 1 to 4 hours is fine. If you're processing logs after the fact, the bulk endpoint accepts up to 50,000 IPs per POST request.
What detection APIs can and cannot do
API-based detection catches known proxy networks. Oxylabs, Bright Data, 922 Proxy, SOAX, IPRoyal, and Smartproxy all operate at scale, and their IP pools are large enough to fingerprint.
What it won't catch: zero-day proxy infrastructure, small botnet-based networks, and proxies that load as separate software modules on compromised devices (a pattern Trend Micro flags as an emerging evasion technique).
Connection-level signals fill part of the gap. JA4+ TCP/TLS fingerprinting can detect proxied traffic based on packet characteristics that differ between direct and proxied connections. But JA4+ requires CDN or WAF-level access and is not something you add with an API call.
The practical recommendation: layer an IP intelligence API with behavioral analysis on your application side. An IP flagged as a residential proxy that also shows high login failure rates and automated navigation patterns is a much stronger signal than any single method alone.
A few extra notes
When traffic outgrows the free tier. The Security API costs 2 credits per lookup. At 150,000 credits/month on the Starter plan, that's plenty for login and checkout checks on a mid-traffic app. If you need per-request lookups on every page load, consider the downloadable residential proxy database instead.
Redis caching pattern. Store the threat_score and is_residential_proxy result with a 10-minute TTL keyed on the IP. Check Redis before the API call. This cuts redundant lookups by 70-80% on session-based traffic without serving dangerously stale results.
Fail-open vs fail-closed, stated explicitly. Every detection integration should document which mode it runs in. If your team can't answer "what happens when the security API is down?" without checking code, that's a gap worth closing before your next incident.
Top comments (0)