Blog / Fingerprinting explained

User agent and client hints: keeping JavaScript and headers in agreement

How the user agent string, Sec-CH-UA headers and getHighEntropyValues fit together, why changing only one of them gives a browser away, and how to check yours.

By Charles Lee · 7 min read

A browser describes itself to websites in two ways at once. The old user agent string travels in every request header and is readable from JavaScript. The newer User-Agent Client Hints split the same facts into separate headers (Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform and more) and a JavaScript API, navigator.userAgentData. On a normal browser all of these agree, because one piece of code writes them all. When something changes one copy and not the others, a site sees two different browsers in one visit.

This guide explains what each copy contains, why the user agent string was frozen, how high-entropy hints work, and how to check that the headers and JavaScript in your browser tell the same story.

The user agent string, and why it was frozen

The user agent string is a single line such as:

textMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/<major>.0.0.0 Safari/537.36

Most of it is history. "Mozilla/5.0", "AppleWebKit" and "like Gecko" were added over the years so that old sites would serve modern pages, and every Chrome-based browser still sends them.

For years the string also carried the exact browser build, the exact operating system version and, on Android, the phone model. That made it a cheap fingerprinting ingredient, sent to every server without asking. So Chrome-based browsers reduced it in stages during 2022 and 2023:

  • the minor, build and patch numbers became 0.0.0, leaving only the major version;
  • the macOS version is frozen at 10_15_7, whatever version you run;
  • Windows always says Windows NT 10.0; Win64; x64, for Windows 10 and Windows 11 alike;
  • on Android the device model is replaced with a fixed K.

The detail did not disappear. It moved into client hints, where a site has to ask for it.

Client hints: the structured copy

User-Agent Client Hints divide the user agent into named values. They come in two tiers.

Low-entropy hints, sent by default

On every request over HTTPS, a Chrome-based browser sends three hints without being asked:

HeaderExample valueWhat it says
Sec-CH-UA"Google Chrome";v="<major>", "Not_A Brand";v="8", ...The brands and major versions
Sec-CH-UA-Mobile?0Whether this is a phone (?1) or not (?0)
Sec-CH-UA-Platform"macOS"The operating system name

The same three values are available to scripts as navigator.userAgentData.brands, .mobile and .platform, in the page and in Web Workers.

High-entropy hints, sent on request

The more identifying values are only given out when a site asks. A server asks by sending an Accept-CH response header listing the hints it wants, and the browser includes them in later requests to that site:

httpAccept-CH: Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Model, Sec-CH-UA-Full-Version-List

A script asks through [getHighEntropyValues](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues), which returns a promise:

jsconst hints = await navigator.userAgentData.getHighEntropyValues([ 'platformVersion', 'architecture', 'bitness', 'model', 'fullVersionList', ]); console.log(hints);

The answer includes the full browser version down to the build, the real operating system version (which is how a site tells Windows 11 from Windows 10 now that the string says the same for both), the processor architecture and bitness, and the device model on Android phones.

GREASE: the brand that is not a brand

The brand list always contains an entry like "Not_A Brand";v="8" or "Not/A)Brand";v="99", with punctuation, version and position that change between releases. This is deliberate. The specification calls it GREASE: by inserting a meaningless brand, browsers stop servers from writing brittle code that expects an exact list. It also means the brand list has a recognisable shape for each browser version, which matters below.

Why changing only JavaScript contradicts the headers

Suppose a tool wants a profile on a Mac to look like Windows. The simplest approach is a script that runs before the page and replaces navigator.userAgent. Look at what a site can now compare:

  1. Header against script. The User-Agent request header and the Sec-CH-UA-Platform header are written by the browser's network code, which the script never touches. The server receives "macOS" in the headers while the page's JavaScript says Windows.
  2. Hints against string. navigator.userAgentData.platform still says "macOS" unless the script also replaced it. Even if it did, getHighEntropyValues is a separate function, and its platformVersion and architecture have to fit a Windows machine too.
  3. Version against version. The major version in the string must match the brands in Sec-CH-UA and the full version list. A user agent copied from a list of "common" strings is often a release or two away from the engine actually running, and the hints report the real one.
  4. Brand list shape. Each release has its own brand order and GREASE entry. A hand-written brand list that looks like no shipped release stands out.
  5. Page against worker. A Web Worker has its own navigator. A script injected into the page does not run there unless it is injected into every worker in time, so the worker gives the original answer.
  6. Platform against everything else. navigator.platform, the fonts installed, the graphics renderer and the TLS handshake all belong to the real operating system. A Windows user agent on top of macOS fonts and an Apple GPU is a contradiction no header can fix.

Changing the header with a command-line switch has the opposite problem: the header moves, and the client hints and JavaScript stay behind. The only way to keep all of them in step is for the code that writes every copy to agree, which is the case on an ordinary browser and in a browser that changes these values in its engine. Our page on engine-level fingerprint protection explains that difference in detail.

How to check your own browser

You can compare both sides in a few minutes.

In the request headers

  1. Open DevTools (F12, or Cmd+Option+I on a Mac) and choose the Network tab.
  2. Reload any HTTPS page and click the first request (the document).
  3. Under Request Headers, find user-agent, sec-ch-ua, sec-ch-ua-mobile and sec-ch-ua-platform. Note the major version and the platform.

In JavaScript

In the Console tab, run:

jsnavigator.userAgent; navigator.userAgentData.brands; navigator.userAgentData.platform; await navigator.userAgentData.getHighEntropyValues(['platformVersion', 'fullVersionList']);

The platform and major version should match what the headers said, and the full version list should begin with the same major version as the string.

With the free fingerprint test

Our browser fingerprint test reads every client hint from JavaScript, the low-entropy ones and the high-entropy ones, and lists them in its identity section: platform, brands, mobile, OS version, processor, device model and full versions. Then three consistency checks do the comparison for you:

  • User agent vs platform compares the string with navigator.platform.
  • User agent vs client hints compares the operating system, major version and mobile flag in the string with the hints and the full version list.
  • Page vs Web Worker reads the same values from a worker and lists any that differ.

The test runs entirely in your browser and makes no network request, so it sees the JavaScript side only. Use DevTools for the header side, as above. If both agree, the two copies are in step.

How Browz keeps them in agreement

Browz changes what a site reads inside the browser engine, not with a script added to the page. That matters most for the user agent, because the engine is the one place that writes the header, the client hints, navigator.userAgentData and the value a worker reads.

  • One source for every copy. The user agent, the client hints and their brand list match the browser version the profile really runs and the operating system it presents, as listed on our anti-detect page. Headers, page, iframes and workers are answered by the same code.
  • The brand list of a real release. Because the version in the hints is the version running, the brand order and GREASE entry are the ones that release ships with.
  • Part of one device. Each profile's platform, fonts, graphics names and screen are derived together, so the user agent is not the only thing that says which operating system the profile is on. You can set the platform and user agent to Auto, Custom or Real per profile on the engines page.
  • The network side agrees too. TLS and HTTP/2 come from the engine's own network stack. We cover that layer in TLS fingerprinting explained.

We run the free test against each release and publish the result. On 13 November 2026, Browz 0.1.9 on macOS passed 15 of 15 consistency checks, including user agent against platform and against client hints, in both a normal window and headless mode; see our own result.

Frequently asked questions

What is the difference between the user agent and client hints?

The user agent is one string sent with every request. Client hints split the same information into separate headers and a JavaScript API, and hold back the most identifying details until a site asks for them. On a normal browser they describe the same browser, version and operating system.

Why does my user agent show an old macOS version?

Because Chrome-based browsers freeze the macOS version in the string at 10_15_7 to make it less identifying. Your real version is still available to sites that ask for the platform version hint, through Accept-CH or getHighEntropyValues.

Is changing my user agent enough to change my fingerprint?

No. The user agent is one value among many, and it is cross-checked against the client hints, the platform, the fonts, the graphics renderer and the network handshake. Changing it alone usually creates a contradiction that is easier to notice than the original value. See fingerprint consistency for the other pairs sites compare.

Do Firefox and Safari send client hints?

At the time of writing, User-Agent Client Hints are sent by Chrome-based browsers. Other browsers rely on the user agent string, so a site that receives no hints from a browser that claims to be Chrome has found a mismatch of its own.

What is Sec-CH-UA-Full-Version-List?

It is a high-entropy hint that lists each brand with its full version, down to the build. A site gets it only after asking with Accept-CH, or by calling getHighEntropyValues with fullVersionList. Its major version should always match the one in the user agent string.

Get started

Every account, every agent. Its own browser.

For Mac, Windows and Linux. Free for 5 profiles.