> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracenow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# How it works

> The complete mental model for tracenow, two layers, one request lifecycle, and how everything fits together.

tracenow protects your app at two layers: an **edge layer** (no code, via Cloudflare) and an **in-app layer** (one backend call). Both layers call the same `/trace` endpoint. Together they cover every attack surface: bots and bad inputs at the door, per-user account-takeover signals inside your app.

## The two layers

<Columns cols={2}>
  <Card title="Edge (no code)" icon="shield-check">
    A managed Cloudflare Worker on your zone intercepts every login, signup, and OTP before it reaches your app. It injects the device fingerprint script, extracts signals, and calls `/trace`. On `deny`, it blocks at the edge. No code required.
  </Card>

  <Card title="In-app (one call)" icon="user-check">
    After you authenticate the user, call `/trace` from your backend with the real `subject_user_id` and confirmed `status`. This unlocks per-user signals the edge can never see: new device for this account, impossible travel, per-user failed-login velocity.
  </Card>
</Columns>

The edge cannot know *who* is logging in because the user isn't authenticated yet. The in-app call fills that gap with one small backend call after authentication.

| Layer                          | When it runs                         | Has `subject_user_id`           | Can block at the edge       |
| ------------------------------ | ------------------------------------ | ------------------------------- | --------------------------- |
| Edge (Cloudflare Worker)       | Before your app receives the request | No (falls back to email as key) | Yes, on `deny`              |
| In-app (`/trace` from backend) | After you authenticate the user      | Yes                             | No, you enforce in your app |

<Note>
  You can start with the edge today (no code, no deploy) and add the in-app call later. The device token is already on the request as `x-tn-device-token`, so there's nothing extra to wire up on the frontend.
</Note>

## Architecture

```
                        ┌─────────────────────────────────────────┐
                        │          Your Cloudflare Zone            │
                        │                                          │
  Browser               │   ┌─────────────────────────────────┐   │
  ─────────────────────►│   │      tracenow Worker            │   │
  POST /login           │   │  1. inject trace.min.js         │   │
                        │   │  2. read email/phone/IP         │   │
  ◄─────────────────────│   │  3. POST /trace ─────────────►  │   │
  loads trace.min.js    │   │  4. verdict returned            │   │
                        │   │  5. deny → block here (403)     │   │   api.tracenow.io
  POST /identify        │   │     allow/challenge → forward ──┼───┼──► enrich + evaluate
  ◄─────────────────────│   └─────────────────────────────────┘   │    policies → verdict
  device token (dv_...) │                      │                   │
                        │                      ▼                   │
                        │              ┌───────────────┐           │
                        │              │  Your Origin  │           │
                        │              │  (your app)   │           │
                        │              └───────────────┘           │
                        │                      │                   │
                        └──────────────────────┼───────────────────┘
                                               │
                                    POST /trace (in-app)
                                    ├── subject_user_id: "usr_123"
                                    ├── status: "succeeded"
                                    └── device_token: from x-tn-device-token header
```

## Request lifecycle (edge layer)

Here is the full sequence for a protected route in Enforce mode.

```
Browser                  Cloudflare Worker            api.tracenow.io       Your Origin
   │                            │                            │                    │
   │── POST /login ────────────►│                            │                    │
   │                            │                            │                    │
   │◄── 200 + <script> ─────────│ (1) inject trace.min.js   │                    │
   │    (served first-party     │     served from your domain│                    │
   │     from your own domain)  │                            │                    │
   │                            │                            │                    │
   │── POST /identify ──────────┼───────────────────────────►│                    │
   │◄── { device_token } ───────┼────────────────────────────│                    │
   │                            │                            │                    │
   │── POST /login ─────────────│ (2) next request carries   │                    │
   │   x-tn-device-token: dv_.. │     the device token       │                    │
   │                            │ (3) extract email, phone,  │                    │
   │                            │     IP, device_token       │                    │
   │                            │── POST /trace ────────────►│                    │
   │                            │◄── { verdict: "deny" } ────│                    │
   │                            │                            │                    │
   │                            │ (4a) verdict = deny        │                    │
   │◄── 403 ────────────────────│     block here             │                    │
   │                            │                            │                    │
   │                            │ (4b) verdict = allow/challenge                  │
   │                            │──────────────── forward to origin ─────────────►│
   │◄──────────────────────────────────────────── response from origin ───────────│
   │                            │                            │                    │
   │                            │ (5) outcome trace (async)  │                    │
   │                            │     maps HTTP response code│                    │
   │                            │     → succeeded/failed     │                    │
   │                            │── POST /trace (status) ───►│                    │
```

### What happens at each step

<Steps>
  <Step title="Inject trace.min.js">
    The Worker serves the device fingerprint script from a path on your own domain. The script runs in the browser, collects device signals (canvas, timing, automation indicators), and calls `/identify`.
  </Step>

  <Step title="Classify the event">
    The Worker checks if the request matches a configured route and method. GET requests are not monitored by default. Matched requests are classified as `login`, `signup`, or `otp`.
  </Step>

  <Step title="Extract signals and call /trace">
    The Worker reads the email/phone from the request body, the IP from headers, and the device token from `x-tn-device-token` (or `tn_device_token` form field). It calls `POST /trace` synchronously (Enforce mode) or asynchronously (Monitor mode).
  </Step>

  <Step title="Enforce the verdict">
    In Enforce mode: `deny` → return the configured block response (default 403, or a custom status, redirect, or body). `allow` and `challenge` → forward to origin. In Monitor mode: always forward; the trace is logged.
  </Step>

  <Step title="Outcome trace (async, non-blocking)">
    After the origin responds, the Worker fires a second `/trace` call to record the real outcome, mapping the HTTP response code to `succeeded` or `failed`. This is what powers failed-login velocity rules at the edge. It does not block the user.
  </Step>
</Steps>

<Warning>
  The Cloudflare Worker cannot render your app's MFA or CAPTCHA flow. A `challenge` verdict is always forwarded to your origin. Handle step-up friction in your app. Only `deny` blocks at the edge.
</Warning>

### Fail-open

If `/trace` is slow or unreachable, the Worker forwards the request to your origin without enforcing. tracenow never breaks your site.

## The device-token round-trip

Device fingerprinting is stateful: the injected script collects signals, fetches a device token from `/identify`, and the browser attaches that token to every subsequent same-origin request automatically, as the `x-tn-device-token` header or `tn_device_token` form field.

You never manage this. The Worker injects the script, the script fetches the token, and the token arrives at your server as a request header. When you make the in-app `/trace` call, you just read it off:

```typescript theme={null}
const deviceToken = req.headers["x-tn-device-token"];
```

The device token lets tracenow recognize the same physical device across sessions and users. This is what powers `device.is_new_for_user`, `device.automation_detected`, and `velocity.device.distinct_user_ids_24h`.

## How /identify and /trace relate

`/identify` and `/trace` are two distinct calls with distinct purposes:

| Call        | Who makes it                                        | When                              | What it does                                                |
| ----------- | --------------------------------------------------- | --------------------------------- | ----------------------------------------------------------- |
| `/identify` | The injected `trace.min.js` script (browser)        | On page load of a protected route | Collects device signals, returns a `device_token`           |
| `/trace`    | The Cloudflare Worker (and optionally your backend) | On a login/signup/otp event       | Evaluates all signals against policies, returns a `verdict` |

You never call `/identify` yourself. The Worker injects the script that calls it. The device token the script receives is what you (or the Worker) forward to `/trace` as `device_token`.

## In-app layer: the one call that adds per-user depth

Call `POST /trace` from your backend after authenticating the user. Pass the real `subject_user_id` and the confirmed `status`. These are things the edge will never know.

```typescript TypeScript (Node) theme={null}
const deviceToken = req.headers["x-tn-device-token"];

const trace = await fetch("https://api.tracenow.io/trace", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRACENOW_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    event: "login",
    status: loginSucceeded ? "succeeded" : "failed",
    subject_user_id: user.id,    // the real account — the edge never has this
    email: user.email,
    ip: clientIp,
    device_token: deviceToken,   // arrived on the request automatically
  }),
}).then((r) => r.json());

switch (trace.verdict) {
  case "allow":     return issueSession(user);
  case "challenge": return requireMfa(user);
  case "deny":      return res.status(403).json({ error: "Blocked" });
}
```

Per-user signals that only unlock here:

* `device.is_new_for_user`: this device has never been seen for this account
* `ip.is_new_country_for_user`: login from a country this user has never used
* `ip.impossible_travel`: physically impossible to have moved between the last location and this one
* `velocity.user.*`: counts scoped to this specific user ID

## Where to go next

<Columns cols={2}>
  <Card title="Verdicts" icon="gavel" href="/concepts/verdicts">
    What allow, challenge, and deny mean, and how to handle each in your app.
  </Card>

  <Card title="Cloudflare integration" icon="plug" href="/guides/cloudflare">
    Connect Cloudflare, configure route rules, and deploy the Worker.
  </Card>

  <Card title="Rules and policies" icon="list-check" href="/guides/rules">
    Build conditions that trigger challenge or deny: fields, operators, logic.
  </Card>

  <Card title="Server-side integration" icon="server" href="/guides/server-side">
    The full in-app /trace contract, every field, and verdict handling patterns.
  </Card>
</Columns>
