Blog · Signals

The User-Agent string is going away: sua, client hints, and what to send now

Chrome finished reducing its default User-Agent string back in Chrome 113, in April 2023. Three years on, most of the industry has stopped thinking about it, but a lot of device-detection code still leans on device.ua as if it carried the detail it used to. OpenRTB 2.6 shipped the fix at launch: a structured device.sua object built on User-Agent Client Hints. It is worth re-checking who in your stack is actually populating and parsing it.

What UA reduction broke

Google rolled out User-Agent reduction in Chrome across seven phases through early 2023, freezing or removing the parts of the UA string that used to identify a device: minor and build version numbers collapsed to 0.0.0, desktop OS versions were unified to static placeholder values, and on Android the device model was replaced with the literal character K and the OS version frozen to 10 regardless of the real one. A UA string that used to tell a bidder "Pixel 8, Android 14" now says nothing more specific than "some Android phone." Any pipeline that inferred device model, exact OS version, or precise browser build from device.ua lost that signal, not gradually, but at a fixed cutover per phase.

The replacement: device.sua

The UserAgent object (OpenRTB 2.6, Section 3.2.29) carries structured data sourced from User-Agent Client Hints and hangs off device.sua. It has been part of the base 2.6 spec since release, not a later dated-snapshot addition. Each brand/version pair uses a nested BrandVersion object (Section 3.2.30): a required brand string plus a version array of components in descending order (major, minor, micro, ...).

FieldTypeNote
browsersarray of BrandVersionDerived from Sec-CH-UA-Full-Version-List
platformBrandVersionDerived from Sec-CH-UA-Platform / -Platform-Version
mobileinteger1 = prefers mobile content, 0 = prefers desktop content
architecturestringe.g. "x86", "arm", from Sec-CH-UA-Arch
bitnessstringe.g. "64", from Sec-CH-UA-Bitness
modelstringDevice model, from Sec-CH-UA-Model
sourceinteger, default 0AdCOM User-Agent Source list: 0 unknown, 1 low entropy UA, 2 client hints

A minimal example on an Android request:

"device": {
  "ua": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 ...",
  "sua": {
    "browsers": [
      { "brand": "Chromium", "version": ["124"] },
      { "brand": "Not.A.Brand", "version": ["24"] }
    ],
    "platform": { "brand": "Android", "version": ["14"] },
    "mobile": 1,
    "architecture": "arm",
    "model": "Pixel 8",
    "source": 2
  }
}

device.sua takes precedence over device.ua

The spec is explicit about ordering: "If both ua and sua are present in the bid request, sua should be considered the more accurate representation of the device attributes. This is because the ua may contain a frozen or reduced user agent string." That is a directive to bidders, not just a note about which field is newer: when both are present, device-detection logic should read device.sua first and fall back to device.ua only when sua is absent. Full object anatomy, including where device.sua sits relative to the rest of Device, is on the bid request doc.

What sellers should populate, what buyers should parse

On the sell side, the spec still recommends always populating ua with whatever User-Agent string the device provides, even a reduced one, for backward compatibility, and populating sua whenever the client supports Client Hints (in practice, Chromium-based browsers and their mobile equivalents; Client Hints are not universal across all browser engines). That means exchanges need a real pipeline for reading the Sec-CH-UA-* request headers, or the equivalent NavigatorUAData accessor client-side, and mapping them onto the UserAgent object rather than leaving sua empty by default.

On the buy side, device-detection logic that still keys exclusively off device.ua is working from a degraded signal on any traffic where sua is available and unused. Model, platform version, and architecture are the fields most worth wiring up first, since those are exactly what UA reduction removed from the string.

Validation angle: sua type mistakes

The most common structural bug is treating browsers or platform as a single object instead of following the spec: browsers is an array of BrandVersion objects (a device can report multiple brands, including deliberately fake "greased" ones like Not.A.Brand used to prevent UA sniffing), while platform is a single BrandVersion object, not an array. The second common bug is sending version as a string ("14.0.1") instead of the required array of components (["14", "0", "1"]). Both are exactly the kind of type mismatch that fails silently in loosely typed pipelines and shows up later as garbage device-detection output rather than a rejected request.

What to check this week

Run a sample request with both device.ua and device.sua through the bid request tester to confirm sua.browsers and sua.platform are structured correctly and sua.version arrays are not flattened into strings. The same checks run in the Rust CLI for CI. If your device-detection vendor or in-house logic has not been updated to prefer sua over ua when both are present, that is worth raising this week rather than after the next round of Chromium changes narrows the UA string further.

Sources