Docs

Integrate in two calls.

One script tag collects signals in the browser and returns a requestId. Your backend then fetches the authoritative result with a secret key. Never trust the client alone.

01 / CLIENT

Load the agent in the page

Paste this before the closing </body> tag. Replace pk_YOUR_PUBLIC_KEY with a public key from your dashboard.

index.html
<script src="https://api.whorlid.com/v1/fp.js"></script>
<script>
  fpjs
    .load({ publicKey: 'pk_YOUR_PUBLIC_KEY' })
    .then(function (fp) { return fp.get(); })
    .then(function (result) {
      // result.requestId  - send this to your backend for verification
      // result.visitorId  - linked id for this browser
      // result.similarity - 0..1 signal-agreement score
      console.log(result.requestId, result.visitorId, result.similarity);
    });
</script>

Public keys are safe to expose in the browser. Restrict which sites can use them with the allowed-origins list in your project settings.

02 / SERVER

Verify with your secret key

The client response is a hint, not a proof. Send the requestId to your backend and fetch the authoritative event with your secret key:

server.sh
curl https://api.whorlid.com/v1/events/{requestId} \
  -H "Authorization: Bearer sk_YOUR_SECRET_KEY"

Response:

200 OK · application/json
{
  "requestId": "0d9e2c9b-...",
  "visitorId": "7a41f3d2-...",
  "similarity": 0.98,
  "decision": "match",
  "isReturning": true,
  "highConfidence": true,
  "bucketRarity": 0.92,
  "isBot": false,
  "botScore": 0,
  "botReasons": [],
  "threatLevel": "low",
  "country": "IE",
  "isDatacenter": false,
  "ip": "203.0.113.7",
  "userAgent": "Mozilla/5.0 ...",
  "matcherVersion": "v2",
  "origin": "https://app.example.com",
  "createdAt": "2026-07-20T09:30:00.000Z"
}

Secret keys (sk_live_...) must never ship to the browser. Create one in your project's API keys tab; it is shown exactly once.

Response field reference

FieldTypeDescription
requestIdstring (uuid)Id of this identification event. Use it for server-side verification.
visitorIdstring (uuid)Identifier the matching engine linked this browser to. It is a probabilistic link, not a permanent hardware id: browsers with anti-fingerprinting protections can receive a new id, and two identically configured devices can be indistinguishable.
similaritynumber (0 to 1)Weighted agreement between this visit's signals and the matched profile. It is a similarity score, not a calibrated probability; compare it against your own thresholds per use case.
decision"match" | "new" | "ambiguous"How the engine resolved this observation (server-verified response only). match: linked to a known device. new: a confidently distinct device. ambiguous: evidence or the margin over the second-best candidate was insufficient, so a fresh id was issued as a fallback. Treat ambiguous as low-trust, not a confident new device.
isReturningbooleanTrue when the visitor was matched to a previously seen device.
highConfidencebooleanTrue when similarity clears the engine's strict match threshold.
ipstringIP address seen by the API (server-verified response only).
userAgentstringBrowser user agent string (server-verified response only).
originstringOrigin of the page that ran the identification.
createdAtstring (ISO 8601)When the identification event was recorded.

Install with AI

Working with a coding agent? Paste this prompt into Claude Code, Cursor, or any AI assistant with access to your codebase. It points the agent at llms.txt (this documentation in machine-readable form) and walks it through both the browser snippet and the server-side verification.

prompt.txt
Integrate WhorlID (a device identification API for fraud prevention) into this codebase, end to end.

Full API reference: http://localhost:3000/llms.txt
Fetch and read that file first. Then:

1. FRONTEND. On the pages where I need device recognition (signup, login, checkout), load the agent and collect a requestId:

   <script src="https://api.whorlid.com/v1/fp.js"></script>
   <script>
     fpjs.load({ publicKey: 'PK_PLACEHOLDER' })
       .then(fp => fp.get())
       .then(result => {
         // send result.requestId to the backend with the form/request it belongs to
       });
   </script>

   The public key is safe to expose. Send requestId alongside my existing form submit or API call; do not act on any other field in the browser.

2. BACKEND. Wherever the requestId arrives, verify it server side before trusting it:

   GET https://api.whorlid.com/v1/events/{requestId}
   Authorization: Bearer SK_PLACEHOLDER

   Store the secret key in an environment variable, never in client code or the repo. The response is the authoritative event; the browser copy is a hint that must not be trusted.

3. USE THE RESULT. Key response fields:
   - visitorId: stable id for this device in this project
   - decision: match | new | low | ambiguous
   - similarity (0..1) and highConfidence: how strong the match evidence is
   - isReturning: whether this device has been seen before
   - isBot, botScore (0..1): automation markers
   - threatLevel: low | medium | high
   - country, isDatacenter: network context

   Suggested starting policy:
   - decision === 'match' && !isBot: treat as a known returning device
   - isBot || threatLevel === 'high' || isDatacenter: challenge or route to review
   - everything else: proceed normally, log visitorId for pattern analysis

4. WIRE MY USE CASE. Ask me which problem I am solving (trial abuse, account takeover, multi-accounting, bot filtering) and add the matching lookup, e.g. counting existing accounts per visitorId at signup.

Replace PK_PLACEHOLDER and SK_PLACEHOLDER with the keys from http://localhost:3000/dashboard (Project > API keys), and tell me what framework this project uses if it is not obvious from the code.

Smart signals

Every /v1/identify call also returns risk and rarity signals computed at identification time:

FieldTypeDescription
isBotbooleanTrue when the browser looks automated (webdriver, headless, and similar).
botScorenumber (0 to 1)Continuous bot likelihood behind isBot (server-verified response only). A strong signal (automation user agent, webdriver) scores 1; weaker hints accumulate. Apply your own threshold rather than relying only on the boolean.
threatLevel"low" | "medium" | "high"Coarse risk rating composed from the other signals.
bucketRaritynumber (0 to 1)How rare this fingerprint's coarse hardware bucket is among your visitors. A device alone in its bucket scores near 1; a very common profile trends toward 0. It is an occupancy measure, not an entropy estimate.
countrystring | nullISO 3166-1 alpha-2 country code derived from the IP, when known.

The browser response keeps the detection details private. Your backend can fetch the full record, including botReasons and isDatacenter, via GET /v1/events/{requestId} with a secret key.

What verification does and does not prove

Fetching an event with your secret key proves one thing: this service recorded that identification call, with these signals, from that IP, at that time. It does not prove the signals came from the physical device they describe. Everything the browser agent collects is public JavaScript output, so a motivated attacker can replay or fabricate signals with a valid public key.

Treat the result as one strong risk input alongside your own session, account, and velocity data. Use it to add friction (step-up auth, review queues, rate limits), not as the sole reason to hard-block a user. For account-security decisions, pair the visitorId with a first-party identifier you control.