Signal · signalsVersion 5

ua_client_hints

Chromium's User-Agent Client Hints, without version numbers: brands (GREASE removed), mobile, platform, and the stable high-entropy hints.

ua_client_hints at a glance (from the library code, signalsVersion 5)
Component nameua_client_hints
In the visitorIdyes
structuredId groupbrowser (engine and build)
Hashingplain value; a digest only when longer than 128 characters or not printable ASCII
Excluded underno browser class
Inner timeout1000 ms
Added insignalsVersion 2
In plain words

Chromium-based browsers (Chrome, Edge, Brave, Samsung Internet and others) describe themselves in a structured way called User-Agent Client Hints: the browser brands, whether it is a phone, the operating system, and on request a few more details such as the CPU architecture. WhiskerJS keeps the parts that rarely change and leaves out version numbers. Other browsers do not offer this, and the component then says so.

What it reads

From navigator.userAgentData:

  • brands: the brand names, without their versions;
  • mobile and platform;
  • from one getHighEntropyValues() call: architecture, bitness, wow64, model and formFactors.

The same call also asks for fullVersionList and platformVersion, which go to the separate ua_versions component. One request per collection is shared by both.

How the technique works

  1. If navigator.userAgentData is not an object, the component is unavailable with no_api.
  2. Brands are filtered: any brand that reads notabrand once reduced to lower-case letters is a GREASE brand (for example Not_A Brand or Not)A;Brand) and is dropped. The rest are sorted by name.
  3. getHighEntropyValues([...]) is called once. Booleans become 1 or 0; form factors are sorted and joined with +.
  4. The value is brands=...;mobile=...;platform=...;arch=...;bitness=...;wow64=...;model=...;form=....
  5. If the detailed call is refused (NotAllowedError or SecurityError, as when a Permissions Policy blocks it), the end of the value is the stable marker he=blocked. Another error gives he=failed; a browser without getHighEntropyValues gives he=none.

Why it helps

It states the device class and architecture more reliably than the user agent string, which Chromium has reduced: mobile, the platform, arm or x86, 32 or 64 bit, the Windows-on-Windows flag, the Android model name and the form factor. Brand names separate Chrome, Edge, Brave and other Chromium builds. Without versions the value stays put across browser updates.

Stability

  • Brands without versions, platform and architecture rarely change.
  • A new GREASE brand in a browser update does not change the value, because GREASE brands are filtered.
  • The value changes if the browser renames a brand, if the page context changes from allowed to refused for the detailed hints (the he=blocked marker replaces the details), or when the device or form factor changes.

Browser support and what blocks it

Browser What you get
Chrome desktop, Chrome Android, Edge, Brave, Samsung Internet ok (Chromium-based)
Safari, every iOS browser, Firefox unavailable / no_api by design
Chromium in a frame or page whose Permissions Policy refuses the detailed hints ok ending in he=blocked

Privacy

The low-entropy part (brands, mobile, platform) is what Chromium already sends to servers by default. The detailed hints add the architecture, bitness and, on Android, the model name. WhiskerJS requests no version numbers for this component, makes no request and shows no prompt. Values over 128 characters or with non-ASCII characters (for example an unusual model name) are hashed.

How WhiskerJS handles noise and failures

Status ruleId When
ok the value, with details or a he=blocked, he=failed or he=none marker
unavailable no_api no navigator.userAgentData
unavailable no_digest the value needs hashing and there is no crypto.subtle
excluded caller_excluded you excluded it
timed_out timeout, aborted the detailed hints did not answer within 1000 ms, or budget or abort

Errors of the detailed call never fail the component; they become a marker. Your server can compare the platform here with the user agent header it receives; see Recommended server checks.

Example value

From headless Chromium in a Linux container: brands=Chromium,HeadlessChrome;mobile=0;platform=Linux;arch=arm;bitness=64;wow64=0;model=;form=Desktop. Firefox and WebKit in the same run: unavailable / no_api. A desktop Chrome on a Mac looks like brands=Chromium,Google Chrome;mobile=0;platform=macOS;arch=arm;bitness=64;wow64=0;model=;form=Desktop.

Questions and answers

What is the GREASE brand and why is it removed?

Chromium adds a deliberately odd brand such as Not_A Brand that changes between versions, so that sites do not depend on an exact brand list. WhiskerJS drops any brand that reads notabrand once reduced to letters, so the id does not change with it.

Does getHighEntropyValues prompt the user?

No. It is a promise-based read with no prompt and no network request. A page or frame can be refused by a Permissions Policy, which WhiskerJS reports with the stable marker he=blocked.

Why are version numbers not in this component?

They change with every browser and operating system update. They are reported separately in ua_versions, which is outside both ids.

Why is it unavailable in Safari and Firefox?

Only Chromium-based browsers implement navigator.userAgentData. Elsewhere the component is unavailable with ruleId no_api, by design.