User-Agent Client Hints mean you now have three signals that must agree
Browsers stopped putting most of the useful platform detail directly in the User-Agent header. That detail did not disappear, it moved into a separate mechanism called User-Agent Client Hints, split across a set of request headers and a JavaScript API. For anyone building or evaluating browser automation, the practical consequence is not that fingerprinting got harder. It is that there are now three places where a browser describes its own platform, and they all have to say the same thing.
The two tiers of hints
Client Hints are split into low-entropy and high-entropy tiers, and the split exists because the high-entropy tier reveals enough to narrow down a specific device, so the browser gates it behind an explicit request.
Low-entropy hints are attached to requests automatically, no opt-in required:
Sec-CH-UA- the browser's brand and significant version, sent as a list (browsers report multiple brand entries by design, including deliberately meaningless ones, so the list itself is not a precise version fingerprint on its own)Sec-CH-UA-Mobile- whether the browser is running in a mobile contextSec-CH-UA-Platform- the OS family, such as"Windows"or"macOS"
High-entropy hints are only sent if the server asks for them, via an Accept-CH response header (or the equivalent meta tag), and the browser then includes them on subsequent requests to that origin. They include things like the full platform version, CPU architecture, bitness, device model, and the complete brand and version list rather than the abbreviated one. The same data is available to page JavaScript through navigator.userAgentData.getHighEntropyValues(), which returns a promise so the browser can gate or throttle access to it.
The three places that must agree
This is the part that matters for anything presenting as a specific browser and platform. A single browser session now states its identity in three independent channels:
- The
User-Agentstring itself - frozen and reduced, but still present and still checked by servers that have not adopted Client Hints. navigator.userAgentData- read by page JavaScript, including whatever detection script the site is running.- The
Sec-CH-UA-*request headers - read server-side, independent of anything the page's JavaScript reports.
A real, unmodified browser produces all three from the same internal state, so they always agree. A spoofing approach that only patches one of them, most commonly the JavaScript layer because it is the easiest to reach with an injected script, leaves the other two showing the browser's real values. A site that checks navigator.userAgentData.platform against the Sec-CH-UA-Platform header it received, or against the substring still present in User-Agent, catches a mismatch instantly. That mismatch is a stronger signal than an ordinary, unmodified fingerprint would have been, because a real device is never internally inconsistent about what it is. Patching one layer and forgetting the other two is how a spoofing attempt manufactures the exact contradiction a detection script is built to look for.
Dump all three yourself
Run this in the browser console to see what JavaScript reports:
(async () => {
console.log('User-Agent string:', navigator.userAgent)
console.log('userAgentData (low-entropy):', navigator.userAgentData)
const high = await navigator.userAgentData.getHighEntropyValues([
'platform', 'platformVersion', 'architecture', 'bitness', 'model', 'fullVersionList'
])
console.log('userAgentData (high-entropy):', high)
})()
And on the server, to see what the request headers actually carried:
// minimal Node http handler, or drop into any Express/Astro route
export function logClientHints(req) {
console.log('User-Agent:', req.headers['user-agent'])
console.log('Sec-CH-UA:', req.headers['sec-ch-ua'])
console.log('Sec-CH-UA-Mobile:', req.headers['sec-ch-ua-mobile'])
console.log('Sec-CH-UA-Platform:', req.headers['sec-ch-ua-platform'])
}
Compare the platform and version reported in all three outputs. In a stock browser they line up by construction. In an automated setup, that agreement is the thing to verify, not assume.
Where you do not need to touch any of this
If your target site only reads the plain User-Agent header and never sets Accept-CH or queries navigator.userAgentData in its scripts, none of the above is in play; the frozen string is all that gets checked, and there is nothing to keep consistent beyond the one header. Most simple scraping and low-volume automation never touches a site that requests high-entropy hints at all, and building consistency logic for a signal the target never reads is wasted effort. Check what the target actually requests before assuming you need to manage all three.
Because AntiBrow applies its fingerprint in the browser engine itself rather than through an injected script, the User-Agent string, navigator.userAgentData, and the Sec-CH-UA-* headers are all generated from the one persona and stay consistent by construction, including across a restart. The general coherence check, covering more than just this one signal, is in the fingerprint consistency checklist. Background on what a fingerprint is and why it is scored at all is in what is browser fingerprinting.
Try it on the free tier.
Unlimited local profiles, no credit card. Check it against the detectors yourself.