Skip to main content
Account takeover is a known, legitimate account suddenly accessed by someone else: the right password from the wrong place. The shape on the wire is a User HID you have seen many times before, arriving on a Device ID you have never seen for it, often from a new country or behind a datacenter, VPN, or Tor connection. ShieldLabs gives you the account’s history to compare against, the durable Device ID to recognize the device, the Risk Score to read the login’s risk signals, and the Account takeover High-Risk Event, so your login flow can step up to a second factor exactly when the device or location does not fit the account.

What is account takeover (ATO)?

Account takeover (ATO) is fraud where someone gains unauthorized access to a legitimate user’s account, usually with stolen or leaked credentials, then uses it to drain funds, make purchases, or harvest data. Because the password is correct, the login passes every credential check and only the device and session context give it away.

How ShieldLabs surfaces it

A first-time login looks the same to your password check whether it is the real owner on a new laptop or an intruder with a stolen password. The difference is in the account’s history. ShieldLabs returns a durable Device ID for the device in front of you, derived on the server from stable device characteristics. It stays the same when the visitor clears cookies, opens an incognito window or rotates IPs, and an intruder on another machine arrives with a different Device ID. A User HID that has only ever appeared on one or two Device IDs, now signing in from a third, is the core takeover shape. The Risk Score (0-100) of the login reads its risk signals on top: Datacenter IP, VPN, Proxy, Tor, Privacy Relay, Anti-detect Browser, Browser Automation and Timezone Mismatch fold into one number. When an intruder fakes a familiar public_ip.country over a VPN, local_ip, the address the browser itself reports, can show a different country. Across the account’s activity, ShieldLabs detects Account takeover as a High-Risk Event on the user, at Medium or High confidence. It is available in the analytics dashboard, the API and webhooks, is a separate axis from the Risk Score, and is keyed on the User HID you pass with checkAuthenticatedUser.
This tutorial is the device-and-location half of login security. The step-up 2FA tutorial owns the threshold ladder that turns a risky login into a second-factor challenge, and the credential stuffing tutorial owns throttling the flood of attempts by Device ID. This page assumes both and only carries the device-comparison logic that is unique to takeover.

Stop account takeover at login

After your password check passes, read the login’s Device ID and Risk Score, compare the device and country against the ones this User HID has used before, and check the account’s worst band over its recent identifications. The login policy: a new device and a new country, or a new device and datacenter or Tor signals on the login, or a new device on an account with a Dangerous history, escalates to a second factor; a strong environment signal escalates on its own. The outcome is that an intruder with the right password but the wrong device meets a challenge the real owner clears and they cannot. ShieldLabs detects the device change, the named signals, and the Account takeover event; you choose the step-up action for each case in your login flow.
The webhook carries two IP objects. public_ip is the public address and its country, which a VPN or proxy can set to match the account’s home region. local_ip is the Local IP: the address the browser itself reports, which can differ from the public IP behind a VPN or proxy and can expose the network behind the mask. local_ip.ip is empty when the Local IP was not captured. detection_flags.ip_mismatch is true whenever the two are different addresses. It is informational, adds nothing to the Risk Score and can be ordinary on mobile networks, so compare local_ip.country with public_ip.country rather than branching on the flag alone. On a takeover-shaped login, a different country behind a familiar public one is supporting evidence.

Build it

1

Create a ShieldLabs account and get your keys

Start Free with 5,000 identifications, one time, no credit card, or log in. In the analytics dashboard, add the domain you want to protect under Integration > Domains, then open Integration > API keys and copy its keys with the copy button next to each. The Public Key loads the snippet in the browser. Keep the server credentials on your backend: the Private API Key reads the History API, and each webhook endpoint has its own whsec_… signing secret. See API keys and Integration.
2

Identify the login and read the Risk Score

Wire the snippet into your login step and call forceCheckAnonymous for every attempt, when the user starts filling the login form (its first focus). A plain check* call would be skipped if the browser was checked in the same visit within the last five minutes, and the login would reach your backend without a request ID. onInitialized fires when the check starts, before the snippet has sent the identification, so do not navigate away inside it; the Login and 2FA tutorial shows the pattern. Pass the hashed User HID only after the password check succeeds, so the account’s history holds only its own signed-in activity: an attempt identified with the User HID before the password is checked would add the device of whoever typed the username to that account. Call forceCheckAuthenticatedUser on the first signed-in page, then checkAuthenticatedUser on every signed-in page. On your server, read the scored result with the shared waitForScore helper: your webhook cache, with a short timeout, falling back to a History API read by request_id.
3

Compare the login device against the account's history

Before issuing the session for an established account, read the devices, countries and worst band of the account from its earlier identifications and compare them to the login in front of you. accountView is the shared helper from the Use Case Tutorials: it reads the account’s identifications by user_hid (newest 100), which hold only its signed-in activity, leaves out the 999 rate-limit marker, and returns the account’s devices, countries and worst band. Reads on account.shieldlabs.ai are free, so this lookup costs nothing.
Compare the login device against the account's history
The account's recent devices and countries
An all-zero Device ID (00000000-0000-0000-0000-000000000000) means no usable device signals reached ShieldLabs for that identification; the rate-limit marker (Risk Score 999) is one such case. Route it to review rather than allowing it: send that login to verification and skip the new-device comparison, since the all-zero id is the absence of a device, not a new one.
4

Escalate the takeover-shaped login

Combine the facts: a new device plus a new country, a new device on a masked login, or a new device on an account with a Dangerous history escalates. Step up rather than hard-block: a real customer buys a new laptop, travels or signs in over a corporate VPN, and a second factor keeps the genuine owner in while it stops an intruder who only has the password.
api/login.js escalate a takeover-shaped login
A new Device ID alone can be a real customer’s new phone, so reserve an outright block for an account already under an active attack, and tune your cutoffs against your own login traffic. The step-up 2FA tutorial owns the band ladder; here the inputs change the rung.
5

Act on the takeover events

The History read above reconstructs one account’s devices on demand. For the standing view across all your users, ShieldLabs detects the account-level shapes as High-Risk Events, each at Medium or High confidence. They are available in the analytics dashboard, the API and webhooks. The Risk Score and risk signals of the login remain the input at the login itself.
One account used from several distinct devices: a spread that can mean a shared or resold account, and at the high end an account being worked from a string of new devices.
The same account appearing in locations it could not reach in the time between them, the shape a hijacked session produces.
An existing account appearing in a new environment that points to someone else using it. The event closest to this page, and the one to watch first.
When an Account takeover event arrives for a user through the API or webhooks, or when you review it in the analytics dashboard, act on the account: add the User HID, with the devices and local IPs linked to it, to a watchlist in your datastore, and at the login check the incoming user_hid and device_id against it. You choose the action for each case. The History API returns every device an account has used, by user_hid. The acting guide gives a starting point for each event.
The user card header for User HID 5e0c2b7d91a4f3c6 in the analytics dashboard: the Dangerous band pill, a red Account takeover pill (High confidence), 9 identifications (7 Trusted, 1 Suspicious, 1 Dangerous) and Details with 2 linked devices and 2 linked countries.The user card header for User HID 5e0c2b7d91a4f3c6 in the analytics dashboard in the dark theme: the Dangerous band pill, a red Account takeover pill (High confidence), 9 identifications (7 Trusted, 1 Suspicious, 1 Dangerous) and Details with 2 linked devices and 2 linked countries.

An account with an Account takeover event in the analytics dashboard.

An account whose environment keeps changing between attempts shows up on each login’s score through the risk signals, including anti-detect browser detection. Use the User HIDs on your Account takeover watchlist at your login gate: any login for one of those accounts gets a second factor regardless of the per-login score, as the watched branch above shows.
Identity continuity rests on the Device ID, not the Visitor ID. The Visitor ID is one device plus one browser cookie, so clearing cookies gives the same browser a fresh Visitor ID. Compare the device a User HID arrives on against the Device IDs it has used before, and treat a Visitor ID change as a weaker hint, not the primary key.

Test it

To confirm device continuity holds, log into the same account from one browser, then clear cookies and log in again, then open an incognito or private window and log in once more. Each login resets the cookie_id and the visitor_id, but the server-derived device_id stays the same, so your newDevice check correctly reads all three as the known device. Now log in from a second browser or a different device: that one returns a device_id your history has never seen for the User HID, which is the takeover shape your gate escalates on. Rotating the IP through a VPN or proxy adds the matching risk signals to the score without changing the Device ID. A guide, not a rule. Layer the conditions: a takeover login trips more than one, and friction should rise as they stack.

Next

Step-up 2FA on Risky Logins

The threshold ladder this tutorial escalates into: when a risky login becomes a second-factor challenge.

Slow Down Credential Stuffing

The other login defense: throttle the flood of attempts on the durable Device ID before takeover is even on the table.