Skip to main content
Every /trace call returns a verdict. There are exactly three. Your code (and the Cloudflare Worker) switches on this value to enforce the outcome.

The three verdicts

allow is the default. It means every rule in your policies evaluated the signals and nothing triggered. There is no block value. The correct term is deny.

allow

No rule matched. The user passed every condition in every policy. Proceed normally: issue a session, complete the transaction, send the OTP.

challenge

A rule matched and its action is challenge. Add friction, but don’t hard-block. Use this for situations where you want the user to prove they’re human before you let them continue: a new device on a known account, a VPN login, a slightly suspicious email domain. challenge means “make them work for it.” The specific mechanism is up to you: MFA, CAPTCHA, email OTP, SMS verification.

Handling challenge in your app

The canonical pattern is a requires_challenge flag in your response body that your frontend interprets:
TypeScript (Node)
Your frontend reads requires_challenge and redirects to your MFA or CAPTCHA flow. After the user completes step-up, call /trace again with the same signals plus status: "succeeded" if they passed. This second trace records the real outcome and re-evaluates against your policies.
After the user completes step-up, send a second /trace call with status: "succeeded". This updates the velocity counters and lets status-dependent rules (like “succeeded login from new country”) evaluate correctly.

Challenge types

Best for account-takeover signals (new device, impossible travel, new country). The user already has an authenticator enrolled. Redirect to your existing MFA flow.
Best for bot/automation signals such as device.is_headless, device.automation_detected, and high velocity. A CAPTCHA distinguishes a real human from a script. Use any provider (hCaptcha, reCAPTCHA, Turnstile).
Best for new-device or low-trust scenarios where the user doesn’t have MFA enrolled. Sends a one-time code to the address or phone number on the account.

deny

A rule matched and its action is deny. Hard block: don’t issue a session, don’t process the transaction, don’t send the OTP. Return an error.

Edge behavior: what the Worker does

The Cloudflare Worker enforces verdicts differently from your backend, because it sits in front of your app and can’t render your MFA flow. The critical difference: at the edge, only deny blocks. challenge is always forwarded to your origin. The Worker cannot render your MFA page or CAPTCHA (that lives in your app), so challenge enforcement is always the in-app call’s job.
If you rely solely on the edge (no in-app /trace call), a challenge verdict will reach your origin as a normal forwarded request with no enforcement. To actually enforce challenge, you need the in-app call. See Server-side integration.
This is intentional and correct. Think of it as division of responsibilities:
  • The edge handles deny (bots, Tor, known-bad IPs, disposable emails): hard blocks before your app sees a single byte.
  • Your app handles challenge (step-up friction for real users who look slightly suspicious).

Configuring the deny response at the edge

In Enforce mode, you configure what the Worker returns on deny. The default is a 403 with an empty body. You can override it per route rule:

When a policy is triggered

When verdict is challenge or deny, the response includes a policy object telling you exactly which rule fired:
Use policy.rule_name for logging. Avoid showing it to the user, as it reveals your detection logic.

When no rule matches

When verdict is allow, the policy object is absent from the response. signals_complete: true means all enrichment resolved. If signals_complete: false, some async enrichment was still pending and the verdict was made on partial data. For high-stakes decisions, retry after ~500 ms to get the warm-cache result.

Full verdict handling pattern

TypeScript (Node)
Python

Where to go next

Server-side integration

The full /trace request and response contract: every field, getting the client IP, and handling signals_complete.

Rules and policies

Define the conditions that trigger challenge or deny: fields, operators, logic, and ordering.