/trace at the edge before every monitored request reaches you, but the edge doesn’t know who the user is yet. This backend call adds the real subject_user_id and confirmed outcome status, which unlocks per-user signals the edge cannot see: velocity.user.*, ip.is_new_country_for_user, device.is_new_for_user, and ip.impossible_travel.
Complete the Cloudflare integration before adding this layer. The Worker
injects the fingerprint script automatically and attaches the device token to every request as the
x-tn-device-token header, so no frontend work is required on your end.When to call it
Call/trace from your backend at the moment you authenticate the user: after you verify credentials but before you issue a session. This is when you have:
- The real user ID (
subject_user_id), which the edge never has - The confirmed outcome (
succeededorfailed): the edge infers this from HTTP status codes, but you know it precisely - The device token, already on the incoming request as
x-tn-device-token
Getting the device token
The Cloudflare Worker injectstrace.min.js first-party onto your pages. That script calls /identify and then attaches the resulting device token to every subsequent request as the x-tn-device-token header. You don’t add any frontend code; just read the header your backend already receives:
TypeScript
Python
Full login example
1
Read the device token from the header
The Worker has already attached it. Extract it before you do anything else.
2
POST to /trace with subject_user_id and confirmed status
Send the real user ID (even on failure, if you resolved the account) and the confirmed outcome.
3
Switch on the verdict
allow → proceed, challenge → require step-up, deny → block.Getting the real client IP
Pass the real client IP, not your server’s outbound address. Extract it from the forwarded headers set by your reverse proxy or load balancer.Handling signals_complete: false
On a first-time lookup for a new IP or email (a cache miss), some async enrichment may not have resolved before the response returns. Thesignals_complete: false flag tells you the verdict was made on partial data.
For most checkpoints this is fine: your policies still evaluated on what was available, and the result is usually correct. For high-stakes decisions (transaction approval, privileged action), retry with the same payload after ~500 ms to get the warm-cache result:
TypeScript
What the in-app call adds
Related
- Cloudflare integration: set this up first
- POST /trace reference: full request and response documentation
- Verdicts: what
allow,challenge, anddenymean and when each applies
