← Back to Content

Browser Fingerprinting in JavaScript: A Developer's Implementation Guide

Implement browser fingerprinting in JavaScript: signals, working code, open-source vs API — and whether device fingerprinting in JS is possible.

Robin
browser fingerprintingdevice fingerprintingvisitor trackingfraud preventionbot detection
Browser Fingerprinting in JavaScript: A Developer's Implementation Guide

Browser fingerprinting in JavaScript lets you recognize a returning browser after cookies are cleared, the session expires, or the visitor opens a private window. The same technique is often searched for as device fingerprinting in JavaScript — and the difference between those two terms matters more than most tutorials admit, so this guide starts there. Then it covers the signals JavaScript can collect, how they combine into a stable identifier, a working implementation with ThumbmarkJS, and the limitations you should design around from day one.

Can You Do Device Fingerprinting in JavaScript?

Not in the strict sense. JavaScript can read device-derived signals — GPU details, screen properties, memory, CPU cores — but only as the browser exposes them, so the identifier it produces is tied to a browser-device pair, not to the device itself. What JavaScript actually does is browser fingerprinting: stable recognition of a returning browser, which is sufficient for most fraud and personalization use cases.

The distinction becomes concrete the moment a visitor switches browsers. The same laptop running Chrome and Firefox produces two different fingerprints, because half the signals in the hash describe the browser's rendering stack, fonts pipeline, and configuration rather than the hardware underneath. Device fingerprinting in the strict sense — persistent hardware identifiers, native mobile SDK identity, recognition across browsers — requires capabilities a web page does not have. Cross-browser matching from hardware signals alone has been demonstrated in research settings, but it has never held up to production standards, and browser vendors have spent years deliberately narrowing the APIs it relied on.

Vendors blur the two terms because "device" sounds more powerful in marketing copy. As an implementer you cannot afford that blur: a fraud rule that assumes one fingerprint per physical device will miscount every user who runs two browsers, and a cart-recovery feature promised to work "across devices" will not. If you want the conceptual foundations before the code, our guide to what browser fingerprinting is covers the topic from first principles.

For the rest of this article, the term is browser fingerprinting — because that is what the code below actually builds.

The Browser Signals You Can Collect — and What Each Contributes

A fingerprint is a hash of many weak signals rather than one strong identifier. Each signal below is individually unremarkable, and some are shared by millions of browsers. Combined, they produce an identifier that distinguishes the large majority of visitors. For the mechanics behind each technique, our breakdown of browser fingerprinting techniques goes deeper than the summaries here.

Two things follow from this list. First, every signal is read through the browser, which is why the resulting identifier is browser-scoped — the point from the previous section, visible now at the API level. Second, any single signal can be spoofed or blocked, but replicating twelve of them consistently is a different proposition. Stability comes from breadth, not from any secret ingredient.

You could collect all of this yourself — the APIs are documented and none of them require permissions. The reason to use a library instead is not the collection, it's everything after: normalizing values that differ across browser versions, excluding unstable components, handling the browsers that randomize canvas output, and hashing the result consistently. That is maintenance work that never ends, because browsers change quarterly.

Implementing Browser Fingerprinting with ThumbmarkJS

ThumbmarkJS is an MIT-licensed browser fingerprinting library used on 60,000+ websites, with an optional API that adds server-side signals when client-side accuracy is not enough. The open-source library collects 10+ signal components — canvas, WebGL, audio, fonts, screen, locales, permissions, plugins and more — and hashes them into a stable identifier. It is free for commercial use, with no account required.

Install from NPM for projects with a build step:

npm install @thumbmarkjs/thumbmarkjs
// Generate a fingerprint and read the core response fields
import { Thumbmark } from '@thumbmarkjs/thumbmarkjs';

const tm = new Thumbmark({ api_key: 'YOUR_API_KEY' });
const result = await tm.get();

console.log(result.thumbmark);                 // fingerprint hash
console.log(result.visitorId);                 // stable visitor ID (API key required)
console.log(result.info.classification.bot);   // bot flag (API key required)

Without an api_key, the same code runs entirely client-side and returns result.thumbmark only — the pure open-source mode. The visitorId and the info object are populated by the API, which the next section covers.

For pages without a build step, load the UMD bundle from a CDN. Note the namespace difference: the class lives under ThumbmarkJS here, not as a bare import:

// CDN usage — no build step required
import('https://cdn.jsdelivr.net/npm/@thumbmarkjs/thumbmarkjs/dist/thumbmark.umd.js')
  .then(() => {
    const tm = new ThumbmarkJS.Thumbmark({ api_key: 'YOUR_API_KEY' });
    tm.get().then(result => {
      console.log(result.thumbmark);
    });
  });

Two implementation details worth knowing before you ship. First, get() never throws — component failures, API errors, and quota limits surface in a result.error array instead, so check that rather than wrapping calls in try/catch. Second, the API key is client-side by design; you secure it by restricting allowed origins in the ThumbmarkJS console, not by hiding it behind your backend.

Open-Source Library vs. API: When Client-Side Fingerprinting Is Enough

The open-source library and the API are two layers of one architecture, and the decision between them comes down to an accuracy number. Purely client-side fingerprinting reaches roughly 80% uniqueness across a million visitors. The API raises that to roughly 99% by combining the client-side hash with signals a browser cannot see: TLS handshake characteristics, HTTP header analysis, and IP/connection details.

The gap has a concrete cause: identical hardware running identical software produces identical client-side fingerprints. Two units of the same phone model on the same browser version can collide — a limitation every client-side library shares, including the ones our comparison of free JavaScript fingerprinting libraries covers. Server-side signals differ even between identical devices, which is how the API separates them. If you are evaluating open-source browser fingerprinting for a production fraud or paywall system, that collision rate is the number to test against your own traffic.

The API also adds signals the client cannot compute: bot classification, VPN and datacenter detection, a 0–5 danger level, and a Visitor ID that stays stable even when the underlying fingerprint drifts after a browser update. The free tier includes 1,000 API calls per month with the same ~99% accuracy — the paid tiers change quota, not quality — so the upgrade decision can be tested before it costs anything. Pro is €15/month for 15,000 calls, a fraction of what commercial alternatives charge for comparable volume.

Common Use Cases — Three Integration Patterns

Nearly every production use of browser fingerprinting in JavaScript reduces to one of three server-side patterns. The seven use cases below are grouped by pattern, so you can map your own problem onto the right mechanic.

Pattern 1: Track fingerprints, challenge the suspicious ones

Store the fingerprints you see against accounts or actions, and add friction only when something does not match.

Pattern 2: Act on classification signals

The API's info.classification object carries the bot, VPN, datacenter, and danger-level signals, so enforcement is a conditional:

// Gate a protected endpoint on bot and threat signals const result = await tm.get(); const c = result.info?.classification; if (c?.bot || c?.datacenter || c?.danger_level >= 4) { return renderChallenge(); // block, CAPTCHA, or rate-limit } serveProtectedContent();

The optional chaining (?.) is deliberate: result.info is only populated when an API key is set, so the guards keep the check from throwing if the key is missing or the API call fails. Note that in those cases the condition evaluates to false and the content is served — decide explicitly whether missing signals should fail open like this or fail closed for your endpoint.

Pattern 3: Key anonymous state on the Visitor ID

Store server-side state against result.visitorId and restore it when the same browser returns — no login required.

Note the scope on that last pattern: recognition works within the same browser. A shopper who adds items on their laptop and returns on their phone is two browser environments — reuniting those requires a login, and no fingerprinting product changes that.

Getting the data to your backend

All three patterns need the fingerprint server-side, and how it gets there affects how much you can trust it. The direct route is forwarding fields from result as request headers to your API — quick to build, but a sophisticated client can spoof its own headers, so treat it as suitable for low-to-medium-risk enforcement. For decisions you need to trust, configure a webhook in the ThumbmarkJS console: on every successful fingerprint generation, ThumbmarkJS POSTs the full payload — classification, uniqueness, visitor ID — directly to your endpoint, bypassing the client entirely. Webhook delivery is asynchronous and unordered, which makes it wrong for real-time block/allow decisions but right for fraud review queues, logging, and building the fingerprint lists that Pattern 1 depends on.

Privacy Modes, Evasion, and the Limits You Should Design Around

Technical readers reasonably ask what happens when visitors try to defeat fingerprinting. The answers are more nuanced than either the marketing or the privacy-forum version.

None of these limits argue against fingerprinting — they define where it fits. Used as one signal in a fraud model, as a recognition layer that survives cookie clearing, or as a bot filter, fingerprinting solves problems that nothing else solves at this price point. As a single source of truth for identity, it was never the right tool.

Where to Start

Browser fingerprinting in JavaScript comes down to collecting a broad set of weak signals, hashing them into a stable identifier, and being clear-eyed that the identifier is scoped to one browser on one device. Start with the ThumbmarkJS open-source library on GitHub — it runs in any project in a few minutes and costs nothing. When your use case needs more than ~80% uniqueness or the bot and network signals, the free API tier (1,000 calls/month, full accuracy) is the low-friction way to test whether the upgrade earns its keep. The seven use cases above, and how teams apply them in production, are covered in more depth on our use cases page.