Anti-Bot

What Is User-Agent Client Hints Fingerprinting?

What Is User-Agent Client Hints Fingerprinting? — conceptual illustration
On this page

User-Agent Client Hints (UA-CH) are structured HTTP headers and a JavaScript API that report the same browser/OS facts the User-Agent string used to carry. Chrome sends Sec-CH-UA, Sec-CH-UA-Platform, and Sec-CH-UA-Mobile on every request, exposes the same data via navigator.userAgentData, and answers requests for high-entropy hints (Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model). Anti-bot systems fingerprint scrapers by checking whether all three sources - the legacy UA string, the Sec-CH-UA headers, and the JS API - tell exactly the same story.

Quick facts

HeadersSec-CH-UA, Sec-CH-UA-Platform, Sec-CH-UA-Mobile (low-entropy, always sent)
On requestSec-CH-UA-Arch, -Bitness, -Model, -Platform-Version, -Full-Version-List
JS mirrornavigator.userAgentData.getHighEntropyValues()
Core checkUA string == Sec-CH-UA headers == userAgentData (all must agree)
GREASESec-CH-UA includes a deliberately random brand entry browsers must tolerate

The three sources that must agree

Since Chrome 89+ the same browser identity is exposed three ways, and they are generated from one internal source on a real browser, so they are always consistent:

  • The UA string - navigator.userAgent and the User-Agent header, now frozen/reduced on Chrome.
  • Sec-CH-UA headers - Sec-CH-UA: "Chromium";v="131", "Not_A Brand";v="24", "Google Chrome";v="131" plus platform and mobile flags on every request.
  • navigator.userAgentData - the JS API, including getHighEntropyValues(["platform","platformVersion","architecture","model","fullVersionList"]).

A scraper that edits the User-Agent header but leaves Sec-CH-UA and navigator.userAgentData at their real (or missing) values is instantly incoherent. Python HTTP clients that send a Chrome UA string but no Sec-CH-UA headers at all are an obvious tell, because real Chrome never omits them.

High-entropy hints and the GREASE trap

Low-entropy hints (brand, mobile, platform) ship on every request. High-entropy hints (full version list, architecture, bitness, model, platform version) are sent only when the server asks via the Accept-CH response header - so an anti-bot endpoint can request them and watch how the client answers. The values must be internally consistent: Sec-CH-UA-Mobile: ?1 with a desktop platform, or Sec-CH-UA-Arch: "arm" with Sec-CH-UA-Bitness: "32" on a claimed Apple Silicon Mac, are contradictions.

The Sec-CH-UA header also contains a GREASE entry - a deliberately fake brand like "Not_A Brand";v="24" whose exact text and punctuation vary by Chrome version. Vendors know the real GREASE patterns per version; a hand-built header with the wrong GREASE string, or with the brands in the wrong order, fails the check. navigator.userAgentData.brands must contain the same GREASE entry as the header.

Why this is hard to spoof by hand

Getting UA-CH right means generating a complete, version-accurate identity across all three surfaces simultaneously: the reduced UA string, every Sec-CH-UA header with correct GREASE and ordering, and a navigator.userAgentData object whose getHighEntropyValues() returns matching platform/arch/model. Change the Chrome major version and all of them must move together.

This is why brand-switching and UA spoofing are gated behind engine-level tooling in serious anti-detect browsers - the engine regenerates all three from one config so they cannot drift. A managed scraping API solves it the same way: it impersonates a real Chrome build end to end rather than editing one header. Patching just the UA string with a Python requests override is the single most common reason a scraper that "looks like Chrome" still gets blocked.

Code example

javascript
// Server-side coherence check an anti-bot endpoint runs
// 1) ask for high-entropy hints
res.setHeader('Accept-CH',
  'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model');

// 2) on the next request, compare all sources
function coherent(req) {
  const ua   = req.headers['user-agent'] || '';
  const chUA = req.headers['sec-ch-ua'] || '';        // '"Chromium";v="131", "Not_A Brand";v="24"...'
  const plat = req.headers['sec-ch-ua-platform'] || ''; // '"Windows"'
  const mob  = req.headers['sec-ch-ua-mobile'] || '';   // '?0'

  if (ua.includes('Chrome') && !chUA) return false;     // Chrome never omits Sec-CH-UA
  if (ua.includes('Windows') && !plat.includes('Windows')) return false;
  if (mob === '?1' && plat.includes('Windows')) return false; // mobile flag vs desktop OS
  const major = (ua.match(/Chrome\/(\d+)/) || [])[1];
  if (major && !chUA.includes('v="' + major + '"')) return false; // version drift
  return true;
}

Related terms

What Is Browser Fingerprinting?
Browser fingerprinting is a technique that identifies and tracks a visitor by combining dozens of small, observable characteristics of their…
What Is Fingerprint Lie Detection?
Fingerprint lie detection is the practice of verifying that the signals a browser reports are internally consistent and untampered, rather t…
What Is Fingerprint Clustering?
Fingerprint clustering is the practice of grouping fingerprints from millions of real visitors by similarity, then rejecting any new visitor…
What Is Headless Browser Detection?
Headless browser detection is the set of probes anti-bot systems use to distinguish a headless or instrumented Chrome session from a real us…
Anti-Bot Vendor Detection Cheatsheet
The first step of any scrape against a protected site is identifying which anti-bot vendor is in front of it. The vendor determines almost e…
What Is Camoufox?
Camoufox is a stealth-focused fork of Firefox with anti-fingerprinting patches applied at the C++ build level. Unlike playwright-stealth, wh…
What Is Anti-Bot Detection?
Anti-bot detection is the set of techniques websites use to distinguish automated traffic from human users — and to block, challenge, or thr…
What Is Browser Fingerprinting Evasion?
Browser fingerprinting evasion is the practice of configuring an automated browser so that the combined fingerprint it presents — canvas, We…
How Do Websites Detect Web Scrapers?
Websites detect scrapers by collecting hundreds of signals across the network, transport, browser, and behavioral layers, then scoring the c…

Concept map

How Client Hints Fingerprinting connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

What is the difference between the User-Agent string and Client Hints?

The UA string is one freeform line that Chrome is freezing/reducing to limit passive fingerprinting. Client Hints carry the same facts as structured, opt-in headers plus a JS API. The key detection point is that they must agree - the UA string, the Sec-CH-UA headers, and navigator.userAgentData all derive from one source on a real browser.

Do I need to send Sec-CH-UA headers from a Python scraper?

If you send a Chrome User-Agent, yes - real Chrome always sends Sec-CH-UA, Sec-CH-UA-Platform, and Sec-CH-UA-Mobile, so their absence flags you. Better still, impersonate a real Chrome build end to end (a TLS-impersonating client or a real browser) rather than hand-assembling headers, because the GREASE token and brand ordering are easy to get wrong.

What is GREASE in Sec-CH-UA?

GREASE is a deliberately fake brand entry (e.g. "Not_A Brand";v="24") that browsers include so servers cannot hardcode the brand list. Its exact text, version, and position vary by Chrome version. Anti-bot vendors know the real patterns, so a wrong or missing GREASE entry is a spoofing tell.

Last updated: 2026-05-30