OpenRTB Adopts Structured User Agents (SUA)
Update: February 2024
As the AdTech participants have started to use the Structured User Agent (SUA), ScientiaMobile has updated its API to better support the SUA. Below is an updated guide to how WURFL users should transform user agent strings and User Agent Client hints into SUA objects that conform to the OpenRTB spec.
In version 2.6 of the OpenRTB specification (issued April 2022), the RTB Project has added a description of a “structured user agent” as part of the user agent object (section 3.2.29). For the Advertising industry, this structured user agent combines information found in another new data format called User-Agent Client Hints. WURFL’s API is already updated to accept User-Agent Client Hints. With the sample code outlined in this blog, WURFL users can transform structured user agents (received from an RTB transaction) into User-Agent Client Hints and feed them into the WURFL device detection process.
The Advent of User-Agent Client Hints
For years, online advertising has leveraged device detection for analytics, targeting, and ensuring that ads can effectively display on devices.
The key source of device information came from analyzing the user-agent string found in the HTTP request that ad publishers receive. You can learn more about the user-agent string and the device detection process here.
The era of the user-agent string as the primary source for device detection started to end a few years ago. Google proposed a new mechanism called Client Hints. Essentially, Client Hints is a negotiation mechanism whereby the client browser and server request and receive information. Google has started to use the Client Hints mechanism to convey much of the same information found in the User-Agent string. User-Agent Client Hints are in the process of rolling out. During this rollout process, User-Agents strings will be frozen, meaning they could be conveying potentially inaccurate information. As of the Q4 of 2023, 61.55% of mobile users agents from Chrome and Chromium were already frozen. Instead, users should opt to use the User-Agent Client Hints. For more information, watch this brief webinar where we answer most of your critical questions about User-Agent Client Hints.
WURFL has already adapted its device detection processes to incorporate User-Agent Client Hints and reconcile them with existing User Agent strings. WURFL’s process ensures continued accuracy.
What is a Structured User Agent (SUA)?
In the advertising ecosystem, the real-time bidding process needed a way to communicate these User-Agent Client Hints. The IAB’s OpenRTB specification version 2.6 (section 3.2.29) has outlined a new “structured user agent” or SUA (also referred to as “device.sua”). Essentially, the structured user agent combines the parameters of the User-Agent Client Hints and passes it as one of the OpenRTB data objects.
The OpenRTB specification section 3.2.18 also refers to the SUA while outlining parameters for the device object. Here is a blog that outlines how WURFL can populate most of the parameters found in the device object.
The parameters listed in the OpenRTB device object are a small subset of what WURFL device detection can actually determine. Advertisers, Supply-Side Platforms (SSP), and Demand-Side Platforms (DSP) rely on WURFL to enrich the core device object with more WURFL device capabilities that are useful for device and technographic targeting. For example, WURFL can provide the price of the device (release_msrp) and the age of the device (release_date).
Receiving and Using the Structured User Agent (SUA)
If an advertising publisher passes the SUA in a OpenRTB transaction, then you can use the code below to break the SUA into its component User-Agent Client Hints and feed it into WURFL’s device matching process. This means that WURFL users receiving a SUA can expect the same accuracy as before. In addition, they can continue to enrich their device information beyond just the narrow sliver of info included in the SUA.
Transforming and Feeding Structured User Agents into WURFL
We have created a sample project written in Java at this github that provides guidance on how to transform the SUA. The code takes a sample SUA object and transforms it into a user-agent client hints (UA-CH) that WURFL can consume.
Below is sample code written in Java that will transform a sample SUA object into UA-CHs that WURFL can consume. The sample code will make use of the WURFL OnSite API for Java to perform the device detection. The sample code will make use of the WURFL OnSite API for Java to perform the device detection.
It is a good practice to forward both the User-Agent string (or the ua OpenRTB object) and the SUA where available. The WURFL API has the intelligence to determine which will provide better detection accuracy.
First, we start with a sample Structured User Agent (SUA) written in JSON format.
{ "browsers": [ { "brand": "Mozilla", "version": [ "5", "0" ] }, { "brand": "AppleWebKit", "version": [ "537", "36" ] }, { "brand": "Version", "version": [ "4", "0" ] }, { "brand": "Google Chrome", "version": [ "106", "0", "5249", "126" ] }, { "brand": "Mobile Safari", "version": [ "537", "36" ] } ], "platform": { "brand": "Android", "version": [ "11", "0" ] }, "mobile": 1, "model": "Infinix X6512" }
First we parse the GREASE’d browser/brands set.
// https://github.com/WURFL/wurfl-sua-demo // Let's build the brands string from the object data StringBuilder brandsBuilder = new StringBuilder(); if (browserInfo != null && browserInfo.getBrowsers() != null) { List<Browser> browsers = browserInfo.getBrowsers(); for (Browser browser : browsers) { if (browser != null) { String brand = browser.getBrand(); List<String> version = browser.getVersion(); if (brand != null && version != null) { brandsBuilder.append("\"").append(brand).append("\";v=\""); for (int j = 0; j < version.size(); j++) { brandsBuilder.append(version.get(j)); if (j < version.size() - 1) { brandsBuilder.append("."); } } brandsBuilder.append("\","); } } } // Remove trailing comma if (brandsBuilder.length() > 0) { brandsBuilder.setLength(brandsBuilder.length() - 1); } }
Then we parse the other properties – platform, platform version, model etc.
String platform = ""; String platformVersion = ""; if (browserInfo != null && browserInfo.getPlatform() != null) { Platform platformObj = browserInfo.getPlatform(); if (platformObj.getBrand() != null) { platform = "\"" + platformObj.getBrand() + "\""; } if (platformObj.getVersion() != null) { platformVersion = String.join(".", platformObj.getVersion()); } } String model = browserInfo != null && browserInfo.getModel() != null ? browserInfo.getModel() : ""; String mobile = browserInfo != null && browserInfo.getMobile().contains("1") ? "?1" : "?0";
Finally, the UA-CHs are assembled into a HTTP Request format and fed into the WURFL device detection process.
// Now we put all the extracted data into a Map to feed the WURFL API Map<String, String> headers = new HashMap<>(); headers.put("User-Agent", ua); headers.put("Sec-Ch-Ua", brands); headers.put("Sec-Ch-Ua-Full-Version-List", brands); headers.put("Sec-Ch-Ua-Platform", platform); headers.put("Sec-Ch-Ua-Platform-Version", platformVersion); headers.put("Sec-Ch-Ua-Model", model); headers.put("Sec-Ch-Ua-Mobile", mobile); // Perform the device detection using the WURFL API now System.out.println("Running WURFL Java API " + WURFLEngine.API_VERSION); String wurflRootPath = "wurfl.xml"; WURFLEngine engine = new GeneralWURFLEngine(wurflRootPath); engine.load(); WURFLRequest req = new DefaultWURFLRequest(headers); Device device = engine.getDeviceForRequest(req); System.out.println("Device id: " + device.getId()); System.out.println("Header Quality: " + engine.headerQuality(req)); System.out.println("Complete Device Name: " + device.getVirtualCapability("complete_device_name")); System.out.println("Advertised Device OS: " + device.getVirtualCapability("advertised_device_os")); System.out.println("Advertised Device OS Version: " + device.getVirtualCapability("advertised_device_os_version")); System.out.println("Advertised Browser: " + device.getVirtualCapability("advertised_browser")); System.out.println("Advertised Browser Version: " + device.getVirtualCapability("advertised_browser_version"));
The complete project including all of the helper methods are available here. When you run it, you should see device detection results like this:
Running WURFL Java API 1.12.11.0 Device id: infinix_x6512_ver1 Header Quality: HEADER_QUALITY_FULL Complete Device Name: Infinix X6512 (Smart HD 6) Advertised Device OS: Android Advertised Device OS Version: 11.0 Advertised Browser: Chrome Mobile Advertised Browser Version: 106.0.5249.126
A few notes about User-Agent Client Hints compatibility
Sec-Ch-Ua
When the Sec-Ch-Ua brands header (and the rest of User-Agent Client Hints) was introduced, all requests were intended to carry a GREASE’d set of low entropy browser brands with a version containing the major version of the browser brand. Some requests could optionally include a higher entropy version of the Sec-Ch-Ua Client Hint called Sec-Ch-Ua-Full-Version-List (which replaced the Sec-Ch-Ua-Full-Version Client Hint). As a result the WURFL API previously mandated that all requests with User-Agent Client Hints include the Sec-Ch-Ua brands header.
As you may have observed, OpenRTB’s structured ua (or sua) object does not include a Sec-Ch-Ua Client Hint equivalent. It instead pairs the brands with the high entropy versions in the browsers field, making it the equivalent of the Sec-Ch-Ua-Full-Version-List Client Hint.
Our original recommendation was to simply pass the higher entropy Sec-Ch-Ua-Full-Version-List Client Hint (rebuilt from the sua browsers field) to the lower entropy Sec-Ch-Ua Client Hint as well. With the upcoming WURFL API 1.13.0.0 release, we’ve made Sec-Ch-Ua optional for requests that also include the higher entropy Sec-Ch-Ua-Full-Version-List Client Hint.
Peculiar Brands
You may have noticed that some sua objects include GREASE’d brands that are not typically seen or are slightly different when compared to requests from browsers with native User-Agent Client Hints support. In WURFL API 1.13.0.0, we’re adding additional logic to address this and other similar sua peculiarities.