Skip to main content
Account takeover (ATO) is the most damaging fraud vector for most products. Attackers buy credential lists, stuff them against your login endpoint, and then monetize whatever they find inside: stored payment methods, loyalty points, personal data. The attack is quiet by design: no new accounts, no unusual emails, just valid credentials moving through your normal login flow. Stopping ATO requires two layers. The Cloudflare Worker catches bots, Tor, and stuffing bursts at the edge. The in-app /trace call (one backend call after authentication) unlocks the per-user signals that detect a real account accessed from a new country, a new device, or an impossible location. You need both to fully close the gap.

Which event this protects

ATO maps to the login event. The Worker fires it on every POST to your login route. The in-app call fires it again after your authentication handler runs, this time with the confirmed subject_user_id and real status.

How it works

Edge layer (no code)

The Worker fires on every POST to your login route:
  1. Reads the submitted email and IP.
  2. Attaches the device fingerprint from the x-tn-device-token header.
  3. Calls /trace with event: login, status: attempted.
  4. Evaluates your ATO policy against velocity and device/IP signals.
  5. deny → blocks at the edge. challenge / allow → forwarded to origin.
At the edge, subject_user_id is null because no one is authenticated yet. User-keyed signals (velocity.user.*, is_new_for_user, is_new_country_for_user) fall back to the submitted email. That’s enough to catch stuffing and bots, but not enough to catch a legitimate-looking login from a stolen password.

In-app layer (one backend call)

After your authentication handler confirms the password and issues a session, send a second /trace with the real subject_user_id and confirmed status. This call unlocks the full per-user signal set. The edge trace is already in the system, so this merges the history correctly. Act on the verdict immediately: challenge means require step-up (MFA, CAPTCHA); deny means block the session.

Signals used

See Signals for the full field reference.

Set it up

Fastest setup: enable the pre-built Account takeover policy templates in Monitor mode instead of building these rules by hand. The new-device, new-country, brute-force, and impossible-travel templates reach full accuracy once you add the in-app call in step 4.
1

Connect Cloudflare (if you haven't already)

In the dashboard under Integration, paste a Cloudflare API token with Workers Scripts: Edit and Zone → DNS: Read scopes. tracenow validates the token and lists your zones.
2

Add a route rule for your login endpoint

Under Integration → Routes, add a rule:
  • Route pattern: e.g. yourdomain.com/api/auth/login or yourdomain.com/login
  • Method: POST
  • Event: login
  • Response mapping: map HTTP 200succeeded, 401 / 403failed
  • Mode: start with Monitor
The response mapping is how failed-login velocity works: the Worker reads your origin’s status code and fires an outcome trace with the real succeeded/failed status.
3

Enable the Account takeover policies

Under Guard, enable the pre-built Account takeover policy templates for the login event in Monitor mode. To customize or add your own rules, see Rules.
4

Add the in-app call for full per-user coverage

In your authentication handler, after confirming credentials and resolving the user, call /trace with the real subject_user_id and confirmed status. See Going further below.This is what unlocks new-device, new-country, impossible-travel, and distinct-IP signals.
5

Switch to Enforce mode

After reviewing Monitor traces, go to Integration → Routes, change the login route to Enforce, and click Deploy.
In Enforce mode, only deny verdicts are enforced at the edge. challenge verdicts flow to your origin; enforce them in your authentication handler via the in-app call verdict.

Rules

You don’t build these by hand. The pre-built Account takeover policy templates cover these patterns and enable in one click. See Rules to customize them or add your own.

What a caught attempt looks like

A stuffing wave produces many traces like this in the event feed: A suspicious successful login (new country) looks different:

Going further

The edge layer stops bots and stuffing without per-user history. The in-app call is what makes tracenow aware of this specific user’s normal behavior: their usual countries, devices, and login patterns. Add this call to your authentication handler after you’ve confirmed the user’s identity:
The device_token is already attached to the request as x-tn-device-token by the Cloudflare Worker. No frontend work needed; just forward the header.
Without subject_user_id, signals like ip.is_new_country_for_user, ip.impossible_travel, device.is_new_for_user, and velocity.user.distinct_ips_24h have no per-user history to compare against. With it, tracenow builds a behavioral baseline per account from the first login. See server-side integration for the full request and response reference.