Impossible Travel False Positives: VPNs, Timing and Location Data
Impossible travel compares a login's IP-derived location with the previous stored point for that user and flags an implied speed that would be physically impossible to travel in the elapsed time. A high result means the raw distance-over-time math crossed a threshold — it is not, by itself, proof of account takeover, and several ordinary situations can trigger one.
The calculation behind the score
POST /v1/risk/check computes the great-circle distance between a user's previous stored location and the current one, divides by the elapsed time since that previous observation, and classifies the implied speed: below 500 km/h is low, 500–900 km/h is medium, above 900 km/h is high (impossibleTravel: true). A real long-haul flight can land near the medium/high boundary, which is why the thresholds exist as ranges rather than a single cutoff — but the calculation itself has no concept of “flight,” “VPN,” or “shared network”; it only sees two points and a duration.
The first observation is never flagged
A user's first-ever check has nothing to compare against: it always returns risk: "low" with a null previousLocation and null distance/speed/elapsed values, and (unless commit: false) becomes the new stored baseline. This means the very first login from a new device or network establishes trust by default — worth knowing if an attacker's first action is also unflagged for the same structural reason.
Situations that commonly trigger a false positive
Switching VPN exit nodes or mobile-network gateways between logins can move the resolved location a long distance with no real travel at all — v1 has no VPN- or datacenter-aware suppression, so this reads identically to genuine impossible travel. Ordinary mobile-carrier IP churn (the same physical location resolving to a different gateway city on different requests) and shared/anycast IP ranges can produce the same effect. None of these involve an attacker; they're artifacts of how IP-derived location works, covered in more depth in the accuracy article.
What a high score doesn't prove
risk: "high" is a raw signal from a distance-over-time calculation — not a verified account-takeover event, not evidence about who initiated the login, and not information about whether the user's credentials were actually compromised. Treat it as one input to your own review or step-up-authentication decision, combined with authentication evidence you already have, not a standalone verdict to act on alone.
A two-observation example
A user's stored baseline is Toronto, observed at 09:00 UTC. A login arrives from an IP resolving to Frankfurt at 09:45 UTC — roughly 6,400 km in 45 minutes, an implied speed far above 900 km/h, so this returns risk: "high" and impossibleTravel: true. That result is consistent with either a compromised account being used from a different continent, or the same legitimate user's traffic suddenly routing through a Frankfurt VPN exit for an unrelated reason — the API returns the same classification either way, which is exactly why it's designed as one input to review, not an automatic block.
What this article does not claim
This describes Watch-IP's own v1 implementation. It does not claim comprehensive account-takeover detection, behavioral analysis, or device fingerprinting — those are different techniques this endpoint doesn't attempt.
Sources
Written by Watch-IP editorial, 2026-09-06. Reviewed against apps/api/src/lib/risk.ts and apps/api/src/durable-objects/session-risk.ts.