Android apps

Detect VPN and proxy users in your Android app

ConnectivityManager can tell you this phone has a VPN up. FindIP Shield scores the network behind each signup, login and checkout from outside the device, and your backend makes the call. One Maven Central dependency, INTERNET permission only.

Free Preview · 10,000 events per site per day · No credit card · Terms apply

VPN Proxy Tor Privacy relay Hosting network Malicious IP
The problem

The phone can tell you about its own VPN. Not much else.

NetworkCapabilities.TRANSPORT_VPN answers one question well: is a VPN running on this device. It says nothing about a proxy further along the path, a residential proxy, a Tor exit or a script replaying your API from a hosting network, and it treats a company VPN like any other.

And whatever the app concludes, a modified app can say the opposite. The answer has to come from outside the device, and the decision from your server.

One event, explained

What one of these looks like in Shield

Every visit and form submit is recorded with a score, the reasons behind it, the session around it, and what the page did. A hosting network or a VPN is used legitimately every day; the reasons are there so you can judge, not a verdict on a person.

A recorded eventSample data

login_attempt

62 · high

/screen/login · a moment ago

Why this risk score
  • vpn_detectedThe IP belongs to a commercial VPN. Common among real users; on its own, no reason to refuse anyone.
  • hosting_networkThe IP belongs to hosting infrastructure rather than a mobile or home network.
  • login_attemptA sensitive action: the reasons above weigh more here than on a screen view.
Session
App
Android app · version 4.2.1
Network
AS64500 · hosting · Germany
Networks in session
2
Screen
login
What happened
Shield recommended
challenge
A verdict for your site to use. On its own it stops nothing.
Observed in the page
Step-up asked
This app's backend verified the session, saw challenge, and asked for an emailed code before signing in. A real customer on a VPN types the code and carries on.
How Shield helps

From the first login event to a decision on your server

The verdict is advice to the app. Your backend makes the call.

Start by monitoring

Create an app site and ship the SDK in a debug build. Nothing is refused: every tracked action shows up with a 0 to 100 score and the reasons behind it.

Read the reasons

vpn_detected, proxy_detected, hosting_network and the rest, each with a plain sentence, plus the session: networks, countries, platform and app version.

Decide per action

A login on a VPN can usually continue; a signup that earns a bonus might ask for a code. Your backend chooses from the recommendation.

Verify on your server

Before the account is created or the reward is paid, post the session ID with your secret key. An app can be modified; the server check is the one that counts.

Tune with rules

Custom rules by platform and app version change the verdict for one app, for example retiring builds below 4.0, without touching your website.

What Shield shows you

The evidence, then the response you choose

Each anonymizer as its own flag

VPN, proxy, Tor, privacy relay, hosting network and malicious IP as separate reasons, with the network and country, on every action the app sends.

Checked by your backend

The app sends the session ID with its request. Your server verifies it with the secret key and gets the recommendation plus the session: platform, networks, countries, IP rotation.

Rules by platform and version

Challenge a country only in the Android app, or block builds below 4.0. A matching rule changes the verdict the app and your backend receive.

Setup

Add it from Maven Central

Android 8 (API 26) or later. One dependency (kotlinx-coroutines), permission INTERNET only, callable from Java too.

Create a mobile app site

Sign in, choose Mobile app, and register your applicationId. You get a public site key for the app and a secret key for your server.

Add the dependency

Add net.findip:shield-android from Maven Central and call FindIPShield.configure(this, siteKey) in Application.onCreate. INTERNET is the only permission.

Track the sensitive action

Call track before the login or signup request and send verdict.sessionId with it. Your backend verifies the session before it acts.

Prefer another route? Building for iOS too? See Shield for iOS apps.

In your code

Configure once in Application.onCreate, then track the action and send the session ID with the request. track runs off the main thread.

// build.gradle.kts
implementation("net.findip:shield-android:1.0.1")

// Application.onCreate
FindIPShield.configure(this, "pub_your_app_key")

// On the login button, in a coroutine
val verdict = FindIPShield.track(ShieldEvents.LOGIN_ATTEMPT, screen = "login")
api.login(email, password, shieldSession = verdict.sessionId)
// verdict.risk.recommendation: ALLOW, MONITOR, CHALLENGE or BLOCK
// No answer within 5 s: UNKNOWN with MONITOR, never ALLOW
Runnable Compose demo

Read the session ID the app sent, verify it with the site's secret key, then allow, ask for a step-up, or refuse. The secret key never ships in the app.

curl -X POST https://shield.findip.net/v1/shield/sessions/verify \
  -H "Authorization: Bearer $FINDIP_SHIELD_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"session_id": "<the X-Shield-Session header>", "event": "login_attempt"}'

# → {"verified": true, "risk_status": "available", "risk_score": 62,
#    "recommendation": "challenge", "session": {"platform": "ios", ...}}
Server verification reference
Boundaries

What a risk score is, and what it is not

An assessment, not proof. A VPN, a hosting network or a changing IP is common for privacy-conscious and corporate users too. Shield explains the reasons so you can decide; it does not pass judgement on a person.
Friction, not a security boundary. Anything decided in the browser can be bypassed by someone who controls it. For decisions that matter, verify the session from your server with your secret key.
Unknown is never safe. Where no intelligence was available the status is unknown. Shield fails open: if a decision does not arrive, the form submits as normal.
Questions

Frequently asked

You can, and it is accurate for a VPN running on this phone. It cannot see proxies elsewhere on the path, hosting networks or Tor exits, and on a rooted device or a modified APK the app can report anything it likes. Shield classifies the network from the server side, and your backend verifies the session.

No. No advertising ID, no ANDROID_ID, no hardware ID, no device fingerprint. A random visitor ID lives in noBackupFilesDir: cleared on uninstall and never copied to another phone by Auto Backup. Strict privacy mode sends no visitor ID at all.

Device or other IDs (the visitor ID) and App interactions (event names): collected, not shared, for fraud prevention and security, encrypted in transit, not used for advertising. If you call identify, also declare the identifiers you pass; they leave the device only as hashes and ciphertext.

track answers within five seconds with an unknown verdict and the recommendation monitor, never allow. The app carries on; your backend decides how much an unknown should count for that action.

Not in this release. Shield judges the network and the session, not whether the APK is genuine. If you need that proof too, pair it with Play Integrity on your own backend.

10,000 events per app site per day, no credit card. Send the sensitive actions, not every screen. Beyond the quota events are refused with a 429 and returned as unknown, never as safe.

Start by watching one real flow.

Create a free Shield site, install it, and look at the first real event before you decide on any response.

Add my app

Next: create a free mobile app site, register your app's ID, and see the first real event from a debug build. Nothing is refused until you decide.

Free Preview · Monitor first · Stop, slow, challenge, or redirect risky submits when you choose