The User-Agent Client Hints Migration: Measuring Devices Without the Full UA String
Chrome is reducing the User-Agent string to a frozen shell. Here's how privacy-first analytics reads device signals via Client Hints without fingerprinting.
The slow death of the User-Agent string
For decades the User-Agent request header has been analytics' workhorse for answering basic questions: which browser, which OS, mobile or desktop? It was also a fingerprinting liability. A full UA string like Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/xxx.0.0.0 Safari/537.36 leaks precise version numbers and platform details that, combined with other signals, help identify individual visitors.
Google's User-Agent Reduction effort has been trimming this string for years. Chrome now sends a reduced UA where the platform version is frozen and the minor version segments are zeroed out. The intent, per Chrome's documentation, is to remove passively-available entropy while preserving a structured, opt-in replacement: User-Agent Client Hints (UA-CH).
For anyone building analytics in 2026, the practical consequence is clear. Parsing the legacy UA string for anything beyond coarse browser family is increasingly unreliable — and increasingly the wrong approach.
How Client Hints actually work
UA-CH splits device information into discrete, requestable hints rather than one dense string. Low-entropy hints are sent by default on every request:
Sec-CH-UA— brand and significant version listSec-CH-UA-Mobile— a boolean?0/?1Sec-CH-UA-Platform— e.g."macOS"
High-entropy hints — full platform version, architecture, model, bitness — are not sent unless the server explicitly asks for them. The server requests them with an Accept-CH response header, and the browser may then include them on subsequent requests. The Sec- prefix means these headers are browser-controlled and cannot be set by scripts, which is a meaningful integrity property.
This inversion matters for privacy. The default posture is minimal disclosure. Additional entropy is a deliberate act, logged in your own response headers, rather than something quietly harvested from every visitor.
The privacy-first reading of this shift
Monoid deliberately stays on the low-entropy side of this boundary. We never send Accept-CH to pull architecture, model, or full platform version, and we never combine the hints we do see into a stable device signature. That would be fingerprinting — reconstructing an identifier from device characteristics — which is precisely what the UA reduction was designed to prevent, and what the ePrivacy Directive's Article 5(3) and browser anti-fingerprinting policies treat as access to terminal equipment information.
The default low-entropy hints are enough for the questions analytics should be asking:
- Browser family and major version from
Sec-CH-UA, for compatibility and support planning. - Mobile vs non-mobile from
Sec-CH-UA-Mobile, for layout and Core Web Vitals segmentation. - Platform from
Sec-CH-UA-Platform, for OS-level breakdowns.
Crucially, these are aggregate dimensions, not per-visitor keys. A boolean and a platform name shared across millions of visitors carry no meaningful identifying power on their own.
Reading hints at the edge
Because Client Hints are ordinary request headers, an edge worker can read them without any client-side script. On Cloudflare, the low-entropy hints arrive on the incoming Request object:
export default {
async fetch(request) {
const brands = request.headers.get('Sec-CH-UA') || '';
const isMobile = request.headers.get('Sec-CH-UA-Mobile') === '?1';
const platform = request.headers.get('Sec-CH-UA-Platform') || '';
// Derive coarse dimensions only; never persist a composite key.
return new Response('ok');
}
};
Note what is absent: no Accept-CH echo, no request for high-entropy hints, no hashing of the combined values. The Sec-CH-UA value is a structured list, so parse it with care — it contains intentional "GREASE" brand entries designed to stop brittle exact-match parsing, a technique the specification borrows from TLS.
What to change in your pipeline
If your analytics still regex-matches the full UA string, three things break over time. Version numbers drift to .0.0.0, platform versions freeze, and mobile detection based on substring matching grows unreliable. The migration path is straightforward:
- Prefer the structured low-entropy hints over UA string parsing.
- Treat the reduced UA string as a fallback for older clients only.
- Resist the temptation to request high-entropy hints "just in case" — every additional hint widens your fingerprinting surface and your compliance exposure.
The broader lesson is consistent with everything we build. Browser vendors are steadily removing passive entropy from the web platform. The right response is not to chase it through new APIs, but to design measurement that never needed it.
Comments
Loading comments…