Chrome сообщает каждому серверу, что ваш телефон работает на Android 10
С 2023 года Chrome на Android отправляет с любого телефона один и тот же user agent: Android 10, модель K. Что это сделало с сопоставлением отложенных диплинков, почему мы не стали чинить это через Critical-CH и как домены ссылок Appy снова получают настоящие версию и модель. А заодно — та же проблема в iOS 26.
Теперь каждый Android-телефон — это «K»
Возьмите Pixel на последней версии Android и Galaxy четырёхлетней давности. Откройте одну и ту же страницу в Chrome на обоих. Если не считать версии Chrome, заголовок User-Agent у них одинаковый: Android 10, модель устройства K.
Это не баг, а User-Agent Reduction: ради приватности Chrome заморозил те части строки, которые помогали снимать цифровой отпечаток устройства. На Android это пришло вместе с Chrome 110. По графику проекта Chromium раскатка началась 7 февраля 2023 года и дошла до всех Android-клиентов 11 мая 2023 года.
Большинство сайтов этого даже не заметили. Зато заметили все, кто сопоставлял установки приложений с кликами.
Один и тот же заголовок: до и после
До Chrome 110
Mozilla/5.0 (Linux; Android 9; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36Chrome 110 и новее
Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36Что это сломало в отложенных диплинках
Отложенный диплинк должен пережить установку. Если стор умеет передать токен, он его передаёт: Google Play и Huawei AppGallery отдают новому приложению install referrer, и сопоставление получается точным. Если не умеет — установка началась с поиска в сторе, с ручной установки APK или из стора без referrer, — остаётся угадывать: найти недавний клик из той же сети и прикинуть, похож ли он на тот же телефон.
Это «похож» раньше держалось на user agent. Клик с Android 14 был плохим кандидатом для телефона, который при первом запуске сообщает Android 15, а клик с Galaxy — для Pixel. После урезания каждый клик из Chrome говорил: Android 10 и K. Версия перестала что-либо отсеивать, и в людной сети — в офисе, в кампусе — все Android-телефоны в здании выглядели одинаково.
Appy читает Android 10; K как есть: ни версии, ни модели. А раз больше в клике ничего не было, все клики с Android из одной сети выглядели одинаково, и Appy не мог отличить телефон вашего нового пользователя от остальных.
Решение по учебнику считает каждый клик дважды
Chrome не удалил эти данные, а перенёс их в User-Agent Client Hints. Несколько подсказок с низкой энтропией приходят с каждым запросом: бренд и мажорная версия браузера, признак мобильного устройства и название платформы. А две нужные нам — Sec-CH-UA-Platform-Version и Sec-CH-UA-Model — относятся к подсказкам с высокой энтропией. Chrome отправляет их, только если сайт запросил их заголовком ответа Accept-CH, поэтому в первом запросе к сайту их не бывает никогда.
Для данных, которые нужны странице уже при первой загрузке, руководство Google по Privacy Sandbox об урезании user agent на Android (27 февраля 2023 года) предлагает Critical-CH. Сервер перечисляет подсказки, без которых не может работать; Chrome видит, что их не было, отбрасывает ответ и отправляет запрос ещё раз, уже с подсказками.
Вся загвоздка — в этом «ещё раз». Для сервиса редиректов такой повтор — это второй переход по той же ссылке: каждый тап на Android считался бы дважды и оставлял бы две записи о клике. Мы лучше сделаем чуть больше работы, чем покажем неверные цифры в дашборде каждого клиента, поэтому Appy не использует Critical-CH.
Что вместо этого делают домены ссылок Appy
Два механизма: один для первого тапа из браузера, другой — для всех последующих.
Chrome на телефоне
acme.appy.to
- 01Первый тап по ссылке
GET /spring HTTP/2 Host: acme.appy.to User-Agent: Mozilla/5.0 (Linux; Android 10; K) … Chrome/154.0.0.0 Mobile Safari/537.36 Sec-CH-UA-Mobile: ?1 Sec-CH-UA-Platform: "Android"Только подсказки с низкой энтропией. В user agent — Android 10, модель K.
- 02Промежуточная страница
HTTP/2 200 Accept-CH: Sec-CH-UA-Platform-Version, Sec-CH-UA-Model Content-Type: text/html; charset=utf-8Страница, которая открывает приложение или стор. Через Accept-CH она запрашивает обе подсказки для всех следующих запросов.
- 03Перед переходом
const hints = await navigator.userAgentData .getHighEntropyValues(["platformVersion", "model"]) → {platformVersion: "16.0.0", model: "Pixel 9", …} navigator.sendBeacon(…)Страница читает настоящие значения, дописывает их в запись этого клика и только потом выполняет переход.
- 04Каждый следующий тап
GET /summer HTTP/2 Host: acme.appy.to Sec-CH-UA-Platform-Version: "16.0.0" Sec-CH-UA-Model: "Pixel 9"Chrome запоминает этот запрос для origin, пока пользователь не очистит данные сайта, так что подсказки теперь приходят в заголовках. Ни скрипта, ни повтора.
Что меняют настоящие версия и модель
Когда в клике есть настоящие версия и модель, клик и телефон должны совпасть и по тому, и по другому. SDK передаёт модель телефона из Build.MODEL при первом запуске приложения, так что их можно сравнить. Клик с Pixel 9 больше нельзя принять за установку на Galaxy, а клик с Android 15 больше не подходит телефону на Android 16.
Сильнее всего это заметно в людных сетях. В офисной сети на сорок телефонов клики с других моделей отсеиваются, и часто остаётся только собственный клик Pixel.
Всё это касается установок, которые приходят без токена. На Android большинство установок по-прежнему первыми решают install referrer от Google Play и AppGallery, а Client Hints нужны для остальных. Без referrer или click ID Appy не может подтвердить установку, поэтому при строгой атрибуции она считается органической, как бы хорошо ни подходил клик.
Одна офисная сеть: до и после
Pixel 9 в людной офисной сети впервые открывает приложение через четыре минуты после того, как владелец нажал на ссылку. Страницу стора, которую открыла ссылка, он закрыл и установил приложение через поиск в Play Store, поэтому referrer не приходит.
iOS тоже заморозила версию
Safari пришёл к тому же другим путём. В примечаниях к выпуску Safari 26.0 Apple пишет, что на iOS 26 и iPadOS 26 Safari теперь сообщает в user agent замороженную версию ОС — последнюю, вышедшую до iOS 26. На практике iPhone на iOS 26 называет себя «iPhone OS 18_6». Токен Version/26.0 дальше в строке по-прежнему соответствует версии самого Safari.
Safari не поддерживает User-Agent Client Hints, так что запрашивать нечего. Appy решает это при сопоставлении: если телефон работает на iOS 26 или новее, клик с iOS 18.6 читается как «26 или новее», а не как точная версия, ведь Safari больше не сообщает, какая именно.
Если вы сопоставляете установки сами
Всё это применимо не только к Appy.
Считайте «Android 10; K» неизвестным значением
Распознайте его и отложите в сторону. Замороженное значение, которое выглядит как данные, вредит сильнее, чем пустое поле.
Запрашивайте подсказки на домене, который обслуживает клик
Отправляйте Accept-CH: Sec-CH-UA-Platform-Version, Sec-CH-UA-Model со своего домена ссылок и читайте navigator.userAgentData при первом визите, когда заголовков ещё быть не может.
Прежде чем включать Critical-CH, посчитайте, во что обойдётся повтор
Повторный переход — это второй запрос. Если вы считаете запросы, дедуплицируйте их или обойдитесь без повтора.
Трактуйте iOS 18.6 как «26 или новее»
Не отклоняйте установку на iOS 26 из-за того, что в её клике была версия 18.6. Считайте это iOS 26 или новее, а не точной версией.
Предпочитайте токены догадкам
Install referrer и click ID надёжнее любого user agent. Угадывайте, только если ничего детерминированного не пришло, и прямо говорите, что это догадка.
Что почитать дальше
Частые вопросы
Почему Chrome пишет Android 10 на любом телефоне?
Из-за User-Agent Reduction. Начиная с Chrome 110, который раскатывали с февраля по май 2023 года, Chrome на Android замораживает в строке User-Agent версию ОС на 10, а модель устройства — на K. Настоящие значения доступны через User-Agent Client Hints.
Как получить настоящую версию Android в Chrome?
Запросите подсказку Sec-CH-UA-Platform-Version заголовком ответа Accept-CH или вызовите на странице navigator.userAgentData.getHighEntropyValues(["platformVersion"]). Заголовок приходит только в запросах, сделанных после того, как Chrome увидел ваш Accept-CH.
Решает ли Critical-CH проблему первого запроса?
Он получает подсказки ценой повтора запроса. Для обычных страниц это работает. Но для всего, что считает запросы, — редиректов, трекинга кликов — повтор выглядит как второй визит, если его не дедуплицировать.
Что Safari сообщает на iOS 26?
Замороженную версию ОС. На iOS 26 и iPadOS 26 Safari сообщает последнюю версию, вышедшую до iOS 26, — на практике 18_6, а токен Version/26 по-прежнему соответствует Safari. User-Agent Client Hints Safari не поддерживает.
Урезается ли user agent в Android WebView и встроенных браузерах?
По умолчанию пока нет. Google сообщил, что стандартный user agent WebView урежут так же начиная с Android 17, а приложения, которые задают собственный user agent, его сохранят.
Продолжить изучение
Deferred deep linking без SDK: как это на самом деле устроено под капотом
Контекст ссылки можно пронести через установку приложения, не встраивая SDK атрибуции. Разбираем, что реально переживает границу стора на Android и iOS, что ломается и во что каждый обходной путь обходится приватности.
Узнайте, какая ссылка принесла каждую установку: SDK Appy для iOS и Android
Appy теперь видит, что происходит с вашими ссылками после стора. Новый SDK показывает установки, события в приложении и выручку по каждой ссылке, QR-коду и кампании и открывает новым пользователям нужный экран.
Deferred deep linking: как это работает и когда применять
Переносите исходный intent ссылки через установку, чтобы новые пользователи попадали сразу на нужный экран, а не на дефолтную главную.
Ищете другое? Посмотрите все темы в блоге.
Сопоставляйте установки по фактам, а не по «Android 10»
Домены ссылок Appy теперь запрашивают Client Hints. Отложенные диплинки и атрибуция установок идут вместе с SDK на тарифе Enterprise, а smart-ссылки есть и на бесплатном тарифе.