Impossible travel detection: use velocity counts to catch it
Updated
Impossible travel is a login pattern where one identity appears in far-apart places in too short a time. FingerPrint Checker does not compute distance or speed. It gives you per-visitor velocity counts on the event, signals.velocity (null when they could not be counted in time): distinct IPs, countries and linked IDs, and events, over the last 5 minutes, 1 hour and 24 hours. You write the rule that fits your product.
What impossible travel is
If an account logs in from one country and ten minutes later from another, either the person has a very fast plane or two people are using the account. The second is far more likely, and it is a classic sign of a stolen password.
Real life adds noise: a VPN, a mobile network and a work trip all move an address around. So the pattern is evidence to weigh, not proof.
What we give you, and what we do not
We do not calculate distance between two places or the speed needed to cover it. What the event carries is a count of how much one device changed in a time window; treat a null as unknown, not as zero.
- signals.velocity.distinctIp: how many different IPs this visitor used in 5m, 1h and 24h.
- signals.velocity.distinctCountry: how many different countries.
- signals.velocity.distinctLinkedId: how many of your linked IDs, such as accounts.
- signals.velocity.events: how many events the visitor produced.
Write the rule yourself
The counts include the event you are reading. A device that was in two countries within one hour is a stronger case than one that used two IPs in a day. Pick numbers that fit your own traffic and test them on past events. The whole velocity object is null when the database was busy, so treat null as unknown.
if (!/^ev_[0-9A-HJKMNP-TV-Z]{20}$/.test(eventId)) throw new Error('bad eventId'); // fail closed
const res = await fetch(`https://api.fpchecker.com/v1/events/${eventId}`, {
headers: { 'X-Secret-Key': process.env.FPCHECKER_SECRET_KEY },
});
if (!res.ok) throw new Error('event lookup failed'); // fail closed
const event = await res.json();
if (event.eventId !== eventId || !(Date.now() - Date.parse(event.createdAt) <= 5 * 60_000)) throw new Error('stale or reused event'); // wrong or old event
if (!(await markEventUsed(eventId))) throw new Error('stale or reused event'); // your store: true only the first time
const { signals } = event;
const v = signals?.velocity;
if (v && v.distinctCountry['1h'] > 1) {
requireStepUp(); // your code: your numbers, your rule
}Or let a rule do it
The console rules can act on velocityIps1h and velocityCountries24h, so you can ask for a human check without code. Run an unsaved rule against a stored event first to see what it would decide.
Pair it with the device
Counts say how much a device moved. They do not say whether it is the account owner. For a login, compare the visitor with the devices the account has used before, and ask for a second step when it is new.
Frequently asked questions
Do you calculate travel speed?
No. We give distinct IP, country and linked ID counts and event counts for 5 minutes, 1 hour and 24 hours. The rule is yours.
Will a VPN cause false alarms?
It can, because a VPN changes the address and country. Read the VPN signal next to the counts and ask for a second step instead of blocking.
What if velocity is null?
The counts could not be computed for that event. Treat it as unknown.
Can I search past events by these counts?
Rules can use them. The events API filters by visitor, linked ID, address and signals such as VPN, proxy and Tor.
Try FingerPrint Checker free
Every new account gets 2,000 free checks. No card required to start.
Create a free account