> ## 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.

# Cloudflare integration

> Connect Cloudflare to deploy tracenow at the edge, with device fingerprinting injected automatically, no snippet to embed, no backend call required to start.

tracenow installs a managed **Cloudflare Worker** on your own zone. The Worker injects the
device fingerprinting script for you and calls `/trace` on every login, signup, or OTP. It
blocks bad traffic before it reaches your origin. You don't add a snippet and you don't write a
backend call to get started. **Connect Cloudflare, pick your routes, click Deploy.**

Once it's running you can add the optional [server-side](/guides/server-side) call to unlock
full per-user account-takeover protection (new device for *this* account, impossible travel,
brute-force on a known user).

## Prerequisites

* A Cloudflare account with your domain added as a zone.
* A Cloudflare API token with exactly two scopes:
  * **Workers Scripts: Edit**
  * **Zone → DNS: Read**

<Tip>
  Create a dedicated token for tracenow at
  [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens).
  Select **Create Custom Token**, add the two scopes above, and scope it to the zone(s) you
  want to protect.
</Tip>

## How it works

```
 Browser ── POST /login ──► tracenow Worker (on your zone) ──────────► your origin
    ▲                          │
    │ loads injected script     │  1. inject trace.min.js (first-party, on your domain)
    │ → /identify               │  2. is this a protected route? (GET requests are skipped)
    │ → device token            │  3. read email + device token + IP
    │   auto-attached to next   │  4. POST /trace  →  verdict
    │   request as header       │  5. deny  →  block here
    │                           │     allow / challenge  →  forward to origin
    │                           ▼
    │                  api.tracenow.io → enrich signals + run policies → allow | challenge | deny
```

* **Monitor mode**: the Worker observes and logs the verdict asynchronously. Zero latency added
  to the request.
* **Enforce mode**: the Worker waits for the verdict synchronously (\~100 ms) and returns a
  configured block response when the verdict is `deny`.
* **Fail-open**: if `/trace` is slow or unreachable, the request flows through to your origin.
  The Worker never breaks your site.

<Warning>
  The edge cannot render your app's MFA flow or CAPTCHA, so a `challenge` verdict is
  **forwarded** to your origin. Only `deny` blocks at the edge. Enforce `challenge` in-app via
  the [server-side call](/guides/server-side) or your own step-up logic.
</Warning>

<Warning>
  Before authentication the Worker has **no `subject_user_id`** because the user hasn't proved who
  they are yet. User-keyed signals (new device, impossible travel) fall back to the submitted
  email as the account key. For full per-user depth, add the [in-app call](#in-app-call) after
  you authenticate the user.
</Warning>

## Set it up

<Steps>
  <Step title="Connect Cloudflare">
    In the [dashboard](https://tracenow.io/dashboard), go to **Integration** and paste your
    Cloudflare API token. tracenow validates the token and lists every zone on your account.
    Your token is encrypted at rest.

    Select the zone(s) you want to protect. tracenow stores the connection and shows a badge
    for each connected zone.

    <Frame caption="Integration: connected state with zone badges">
      <img src="https://mintlify.s3.us-west-1.amazonaws.com/tracenow/images/cloudflare-connected-zones.png" alt="Integration page after connecting, with zone badges visible" />
    </Frame>

    **📸 Screenshot to add:** the Integration page right after connecting. Show the zone badges and the **Deploy worker** button.
  </Step>

  <Step title="Configure route rules">
    Under **Routes**, add a rule for each endpoint you want to protect. Each rule maps a
    **route pattern + HTTP method** to an **event type**.

    | Field         | Description                                               |
    | ------------- | --------------------------------------------------------- |
    | Route pattern | e.g. `/auth/login`, `/api/v1/signup`, `/otp/send`         |
    | Method        | `POST` is the default; `GET` requests are never monitored |
    | Event         | `login`, `signup`, or `otp`                               |
    | Mode          | `Monitor` (observe only) or `Enforce` (act at the edge)   |

    **Start in Monitor mode.** Let traffic flow for a day or two to see what your baseline
    looks like before switching to Enforce.

    <Tip>
      tracenow suggests route patterns automatically based on common paths. You can accept the
      suggestion or type your own.
    </Tip>
  </Step>

  <Step title="Configure the blocked response (Enforce mode only)">
    In Enforce mode, when the verdict is `deny`, the Worker returns a response you control. The
    default is a `403` with an empty body. You can override it per-route:

    | Option           | How to configure                   | Best for                              |
    | ---------------- | ---------------------------------- | ------------------------------------- |
    | Status code only | Enter a status (e.g. `403`, `429`) | Simple API consumers                  |
    | 302 redirect     | Enter a URL                        | Sending users to an error page        |
    | Custom HTML body | Paste an HTML page                 | Rendering a branded error to browsers |
    | Custom JSON body | Paste a JSON object                | API / XHR clients                     |

    tracenow content-negotiates: if the request `Accept` header prefers `application/json` and
    you've configured both HTML and JSON bodies, the JSON body is returned.
  </Step>

  <Step title="Deploy">
    Click **Deploy worker**. tracenow pushes the Worker to Cloudflare and wires the route on
    your zone. The deployment takes a few seconds.

    <Note>
      Every time you change a route rule or the blocked response, click **Deploy** again to push
      the updated config. Changes don't take effect until you redeploy.
    </Note>
  </Step>
</Steps>

## Verify it's working

Make a real login or signup through your site. Within a few seconds it should appear in the
**Events** feed.

<Frame caption="Event feed: a login trace with verdict">
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/tracenow/images/cloudflare-event-feed-trace.png" alt="Event feed showing a login trace with signals and verdict" />
</Frame>

**📸 Screenshot to add:** the Events feed showing a recent login trace. Expand one row to show the signals panel (IP, device, verdict).

Each trace shows the signals collected, which rule matched (if any), and the verdict. If you
see `signals_complete: false` on a trace, refresh after \~500 ms. Some async enrichment may
still be resolving.

## Monitor vs Enforce: when to use each

| Mode    | Latency         | Enforcement           | Use when                                         |
| ------- | --------------- | --------------------- | ------------------------------------------------ |
| Monitor | Zero (async)    | None (observe only)   | First week, learning your baseline               |
| Enforce | \~100 ms (sync) | `deny` blocks at edge | You've reviewed signals and are ready to enforce |

<Info>
  In Monitor mode, rules still evaluate and verdicts are recorded. You just don't block
  anything. This lets you tune rules before they have any impact on real users.
</Info>

See [Rules](/guides/rules) for how to build and tune policies. See
[Verdicts](/concepts/verdicts) for the full `allow / challenge / deny` semantics.

<h2 id="in-app-call">
  Add full per-user protection (the in-app call)
</h2>

The Cloudflare Worker can't know *which* account is logging in: the user isn't authenticated
yet. For signals like "new device for this account" or "impossible travel," call `/trace` from
your backend after you verify credentials, with the real `subject_user_id` and confirmed
`status`.

The device token is already on the request: because the injected script auto-attaches it to
same-origin requests, it arrives at your server in the `x-tn-device-token` header. Read it and
forward it.

<CodeGroup>
  ```typescript TypeScript (Node) theme={null}
  // After your server has authenticated the user
  const deviceToken = req.headers["x-tn-device-token"] as string | undefined;

  const trace = await fetch("https://api.tracenow.io/trace", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.TRACENOW_SECRET_KEY}`, // tn_live_...
      "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,  // auto-arrived on the request
    }),
  }).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" });
  }
  ```

  ```python Python theme={null}
  # After your server has authenticated the user
  import httpx, os

  device_token = request.headers.get("x-tn-device-token")

  trace = httpx.post(
      "https://api.tracenow.io/trace",
      headers={"Authorization": f"Bearer {os.environ['TRACENOW_SECRET_KEY']}"},
      json={
          "event": "login",
          "status": "succeeded" if login_succeeded else "failed",
          "subject_user_id": user.id,
          "email": user.email,
          "ip": client_ip,
          "device_token": device_token,
      },
  ).json()
  ```
</CodeGroup>

See [Server-side integration](/guides/server-side) for the full `/trace` contract, every
request field, and the complete response shape.

## Redeploy after changes

Any change to route rules, event mappings, modes, or blocked responses requires a redeploy:

1. Make your changes in the dashboard.
2. Click **Deploy worker**.
3. Cloudflare propagates the new Worker globally in seconds.

## Disconnect

To remove the integration, go to **Integration → Disconnect**. tracenow removes the Worker
from your Cloudflare zone, deletes the route binding, and revokes the stored token. Existing
trace data is preserved.

## Next steps

<Columns cols={2}>
  <Card title="Rules" icon="filter" href="/guides/rules">
    Build policies: conditions, actions, ordering, and worked examples.
  </Card>

  <Card title="Server-side integration" icon="code" href="/guides/server-side">
    The full `/trace` call: every field, the response shape, and verdict handling.
  </Card>

  <Card title="Verdicts" icon="circle-check" href="/concepts/verdicts">
    What allow / challenge / deny mean at the edge vs. in-app.
  </Card>

  <Card title="Signals" icon="signal" href="/concepts/signals">
    Every field you can write rules against.
  </Card>
</Columns>
