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.
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.
Canvas rendering. Draw text and shapes to a hidden <canvas>, then hash the pixel output. GPU, driver, and font-rendering differences produce subtly different bitmaps per machine — the most distinctive single signal available to JavaScript.
WebGL parameters. The GPU renderer and vendor strings exposed through the WebGL context. Highly stable: they change only when hardware or drivers change.
Audio processing. Run a silent buffer through the Web Audio API and hash the output. Floating-point differences between audio stacks yield small, consistent variations.
Installed fonts. Which fonts are present and how they render at a given size. Font sets vary with the operating system and installed software.
Screen and hardware properties. Resolution, color depth, pixel ratio, deviceMemory, hardwareConcurrency. Individually common, collectively useful entropy.
Browser environment. Timezone, languages, plugins, permissions state. Cheap to read and effective at separating otherwise similar machines.
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.
New account fraud. At signup, check the fingerprint against ones you have already seen. A fingerprint on its fifth "new" account this week gets a verification step; everyone else proceeds untouched. Details in our guide to preventing new account fraud.
Account takeover and credential fraud. Store each account's trusted fingerprints. A login from an unrecognized fingerprint — even with the correct password — triggers a challenge. This trusted-list pattern is the practical defense in our account takeover prevention playbook, and the same lists blunt credential stuffing specifically.
Promo code abuse. Validate promo redemptions against fingerprints that have already redeemed, and maintain a list of known abusers. Our article on promo code abuse covers the redemption-check flow.
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.
Bot detection. Check the bot flag before serving sensitive content or processing payments — the setup and signal details are in our bot detection guide.
Web scraping prevention. Scrapers overwhelmingly run from datacenter infrastructure, which the datacenter flag identifies. Our web scraping prevention article covers layering this with rate limiting.
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.
Metered paywalls. Count content consumption per visitor ID server-side and show the paywall past the limit. Clearing cookies no longer resets the meter — the full implementation is in our metered paywall guide.
Persistent carts and personalization. Store cart contents or preferences keyed on the visitor ID and restore them on the next visit from the same browser. The approach behind both cart abandonment reduction and cookieless personalization is the same mechanic with different payloads.
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.
Incognito and private windows do not change the fingerprint. Private mode isolates cookies and storage, but canvas rendering, GPU strings, fonts, and hardware properties are identical to the normal window. This is precisely why fingerprinting survives where cookies fail.
Canvas blockers degrade, not defeat. Extensions like Canvas Blocker randomize canvas output, taking the strongest signal off the table. The fingerprint is then computed from the remaining components at reduced — not zero — accuracy.
VPNs and IP rotation alone change nothing. With a constant browser environment, an IP change does not produce a new fingerprint. The visitor's IP does contribute to the default fingerprint, however — so the combination that does defeat it is a browser reset plus a simultaneous IP change. Design for that with the patterns above rather than assuming the identifier is unbreakable.
A different browser or device is a different fingerprint. This is the structural limit from the top of the article, restated as an operating constraint: there is no JavaScript-only continuity across browsers or devices. Anyone selling cross-device recognition from a script tag is selling something the platform cannot deliver.
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.