Server-side GTM не решает вопрос согласия за вас. Собственные теги Google читают параметры gcs и gcd и следят за собой сами; Meta CAPI и другие не-Google теги срабатывают по своему триггеру, пока вы не загейтите каждый из них. Миграция к тому же убирает три страховки, которые раньше прикрывали небрежную настройку согласия, поэтому на моих глазах это самый частый способ превратить нормально работавшую реализацию consent в дырявую.
Фраза, которую я слышал на вводных созвонах чаще, чем могу сосчитать, и обычно от толкового инженера:
«Мы уходим на server-side, так что с согласиями вопрос закрыт».
Не закрыт. Переход на server-side меняет, где срабатывает тег. Он никак не меняет того, был ли тег вправе сработать.
Четыре сигнала, коротко
В Consent Mode v1 их было два: ad_storage и analytics_storage. В v2 добавились ad_user_data и ad_personalization, и с марта 2024 года Google начал требовать v2 для трафика из ЕЭЗ.
| Сигнал | Чем управляет |
|---|---|
ad_storage | Хранение рекламных идентификаторов (cookies) |
analytics_storage | Хранение аналитических идентификаторов |
ad_user_data | Можно ли отправлять данные пользователя в Google для рекламы |
ad_personalization | Можно ли использовать эти данные для персонализированной рекламы / ремаркетинга |
Для разговора про server-side это разделение принципиально. Первые два - про хранение в браузере, и действуют они именно в браузере. Два новых сигнала v2 - про передачу и использование, а передача это ровно то, чем серверный контейнер занимается по работе. Пользователь может дать analytics_storage и отказать в ad_user_data, и если ваш серверный контейнер этого различия не читает, он спокойно перешлёт событие в Google Ads.
Как согласие должно доходить до сервера
Серверная документация Google описывает три шага:¹
- «Баннер согласия на вашем сайте получает выбор пользователя и передаёт его в Google tag».
- «Google tag передаёт предпочтения пользователя в серверный контейнер, добавляя параметры согласия в HTTP-запрос».
- «Теги продуктов Google на сервере учитывают согласие и корректируют объём и характер отправляемых данных в соответствии с предпочтениями пользователя».
На уровне провода это параметры gcs и gcd. gcs - кодировка из v1: G1xy, где x это согласие на рекламные cookies, а y - на аналитические, каждый 1 или 0. gcd - преемник из v2: он кодирует все четыре сигнала плюс то, каким образом каждое состояние было получено, и едет вместе с хитами в сервисы Google даже тогда, когда Consent Mode вообще не активен.²
Это первое, что нужно проверить в любой доставшейся по наследству настройке. Откройте preview серверного контейнера, возьмите входящий запрос и посмотрите на сырую query-строку. Если gcs и gcd там нет, браузер вашему серверу ничего не сообщил, и третьему шагу просто не с чем работать.
Фраза, которую все читают неправильно
На той же странице Google прямо пишет: «настраивать consent mode нужно только в веб-контейнере».¹
Фраза верная - и она верна про собственные теги Google. GA4, Google Ads, Floodlight: у них есть встроенные проверки согласия, и они сами подстроят своё поведение под gcs/gcd без каких-либо серверных настроек с вашей стороны.
Ко всему остальному в вашем контейнере это не относится. Meta CAPI, TikTok Events API, Snapchat CAPI, тег Klaviyo, ваш собственный custom HTTP request tag - встроенных проверок согласия нет ни у одного из них. Они срабатывают, когда срабатывает их триггер. Если триггер это «пришло событие GA4», они сработают на каждом событии, от каждого пользователя, включая тех, кто отказал во всём.
И в самом интерфейсе GTM здесь есть ловушка. У каждого тега есть настройка Additional Consent Checks с двумя вариантами: No additional consent required и Require additional consent for tag to fire. Первый вариант не означает «этому тегу согласие не нужно». Он означает «GTM ничего не будет проверять перед срабатыванием тега» - тег сработает в любом случае.³ Для тега Google со встроенными проверками это нормально, тег следит за собой сам. Для тега Meta CAPI это открытая труба.
Почему server-side делает ситуацию хуже, а не лучше
Три страховки, которые вы теряете в день миграции:
Браузер больше ничего не может заблокировать. На клиенте пользователь с блокировщиком или строгой CMP блокировал пиксель Meta и Google tag независимо, прямо у источника. На сервере один разрешённый запрос к вашему first-party эндпоинту разлетается по пяти вендорам внутри контейнера, и браузер не видит ничего из этого, не говоря уже о праве вето. Ту логику согласия, которую вы не реализовали, за вас ниже по цепочке не реализует никто.
First-party поддомен убирает случайный страховочный слой. В этом и весь смысл sGTM: ваш эндпоинт - data.example.com, ITP не считает его третьей стороной, блокировщики его почти не трогают. Следствие: грубые, топорные защиты, которые раньше отлавливали какой-то процент ваших ошибок, теперь не отлавливают ни одной.
Серверный контейнер видит данные, которые браузеру были недоступны. Обращения к CRM, маржа заказа, user ID, хешированные идентификаторы, lifetime value. Ради этого sGTM и разворачивают. По этой же причине провал по согласию на сервере - событие принципиально более крупное, чем тот же провал на клиенте: payload богаче.
Четыре вещи, которые нужно реально настроить
1. Передавать состояние согласия. CMP обновляет согласие в веб-контейнере, а Google tag несёт gcs/gcd на сервер в каждом запросе. Проверяйте в серверном preview, а не в дашборде самой CMP.
2. Разложить его в переменную. В серверном контейнере создайте переменную Event Data, читающую входящее состояние согласия, чтобы было на чём строить условия. Назовите её явно - Consent — ad_user_data, а не var3.
3. Загейтить по ней каждый не-Google тег. Либо добавьте переменную в условие триггера, либо используйте Require additional consent for tag to fire с нужными сигналами. Какие именно сигналы - зависит от вендора: рекламному CAPI как минимум нужен ad_user_data, а если вы кормите аудитории ремаркетинга, то и ad_personalization. Делать это придётся тег за тегом. Переключателя на уровне контейнера не существует.
4. Протестировать состояние denied. Этот шаг пропускают практически в каждой настройке, которая мне доставалась по наследству. Поставьте все четыре сигнала в denied, проведите реальную покупку от начала до конца и посмотрите на исходящие запросы серверного контейнера. Если наружу ушёл хотя бы один вызов CAPI или Events API, вы не соответствуете требованиям - зато у вас появился воспроизводимый тест, который можно прогонять после каждого изменения контейнера. Настройки consent ломаются не на запуске. Они ломаются через три месяца, когда кто-то добавляет тег и копирует триггер у соседнего.
Половина, которую consent не покрывает: что лежит в payload
Consent определяет, можно ли отправлять. Он ничего не говорит о том, что вы отправляете. Это два разных гейта, и именно на server-side они складываются - потому что обогащение происходит в серверном контейнере.
Я работал со страховым продуктом внутри суперприложения крупного банка, где реализация аналитики по меркам consent была в порядке. Пользователи согласились на аналитику. Проблема была в payload: ИИН ехал в data.event_info.user.iin в каждом событии, и то же поле плюс телефон, имя и фамилия дублировались в user properties. ИИН - государственный идентификатор; местный закон о персональных данных запрещает передавать его в системы в незашифрованном виде. Зелёный баннер согласия не делает это законным, и никакой чекбокс согласия в этом флоу такое бы не поймал.
Починка состоит из четырёх частей, и все они напрямую переносятся на серверный контейнер.
Конверт идентичности. Аналитика получает UUID и больше ничего. Соответствие UUID → ИИН / имя / телефон живёт в CRM, за ролевым доступом. Аналитик, которому нужно посмотреть на конкретного пользователя, идёт по маршруту CRM → UUID → аналитика, а обратный джойн делается в BI только через защищённую вьюху, которую контролируют юристы и комплаенс. Поведение и идентичность лежат в разных системах с разными списками доступа, а их соединение - осознанное и залогированное действие.
Санитайзер в пути отправки, а не в документации. Одна функция, которая вырезает известные PII-ключи из каждого payload перед отправкой и оборачивает каждый вызов track, - так защита становится структурной, а не зависящей от того, кто будет писать следующее событие. В серверном контейнере ей место именно в контейнере, потому что там прицепляется обогащение из CRM и потому что там однажды услужливый инженер добавит customer_email открытым текстом, чтобы что-то отладить.
Зачистка того, что уже записано. Убрать поле из новых событий - не значит убрать его из профилей пользователей, куда оно уже записано. Это проход $unset через Identify API по существующей базе: в том проекте - порядка 50 000 пользователей, и планирование пропускной способности было отдельной строкой работ, а не сноской.
Гейт, который переживёт вас. Blocked properties, объявленные в схеме событий, чтобы платформа отбивала PII-поля на приёме; CI-линтер, который грепает известные PII-ключи и валит сборку; дашборд с числом событий с PII в сутки и целью ноль. Критерий приёмки: ноль PII-полей в свойствах событий, ноль в свойствах пользователя, user_id - это UUID, а не legacy-числовой идентификатор.
Consent Mode v2 не поймал бы из этого ничего. Он для этого и не сделан. Это продуктовое требование Google про рекламные сигналы; ваш местный закон о персональных данных - отдельная обязанность с другой областью действия и другим регулятором, во многих юрисдикциях более строгим.
Чек-лист, который можно прогнать по унаследованному контейнеру
-
gcsиgcdприсутствуют во входящих запросах в серверном preview - Согласие разложено в именованные переменные Event Data в серверном контейнере
- Каждый не-Google тег загейчен по нужному сигналу - перечислены тег за тегом, а не «предполагается»
- Тест в состоянии denied прогнан от начала до конца, ноль исходящих вызовов вендорам, результат записан
- Тест в состоянии denied включён в релизный чек-лист для изменений контейнера
- Payload проверен на PII независимо от consent - и свойства событий, и свойства пользователя
- Обогащение из CRM в серверном контейнере разобрано поле за полем на предмет «а это правда нужно»
- Схема blocked properties и CI-линтер по известным PII-ключам
- Монитор «событий с PII в сутки», цель - ноль
Что из этого следует
Server-side tagging переносит ваши теги из браузера пользователя на инфраструктуру, которой управляете вы. Всё, что раньше было чужим правоприменением - блокировщик, браузер, блокировка скриптов со стороны CMP, - становится вашим кодом.
Два гейта, настраиваемые отдельно. Consent решает, можно ли отправлять событие. Data governance решает, что в нём может быть. Настройка, которая проходит первый гейт и проваливает второй, - это та самая, которая заканчивается письмом от регулятора, и из двух провалов она встречается чаще, потому что баннер тестируют все, а payload не читает никто.
Источники
Частые вопросы
Если я отправляю данные только в GA4 и Google Ads, нужны ли настройки согласия в серверном контейнере?
Для этих тегов нет. Google пишет, что consent mode нужно настраивать только в веб-контейнере, и это верно для собственных тегов Google: у GA4, Google Ads и Floodlight есть встроенные проверки согласия, и они подстраивают поведение под gcs и gcd. Но проверьте в серверном preview, что оба параметра реально доходят, потому что без них серверу не с чем работать.
Есть ли одна настройка, которая применяет согласие ко всем тегам серверного контейнера?
Нет, переключателя на уровне контейнера не существует. Каждый не-Google тег гейтится отдельно: либо переменной Event Data с состоянием согласия в условии триггера, либо через Require additional consent for tag to fire с нужными сигналами. Настройки обычно ломаются через три месяца после запуска, когда кто-то добавляет тег и копирует триггер у соседнего, поэтому тест denied нужно прогонять после каждого изменения контейнера.
Пользователи дали согласие на аналитику. Значит, можно отправлять ИИН в событиях?
Нет. Consent определяет, можно ли отправлять, а не что вы отправляете. В страховом продукте внутри суперприложения банка пользователи согласились на аналитику, но ИИН ехал в каждом событии, а местный закон о персональных данных запрещает передавать его в системы в незашифрованном виде. Consent Mode v2 - требование Google про рекламные сигналы; закон о персональных данных - отдельная обязанность с другим регулятором.
Если сегодня перестать отправлять PII-поле, проблема решена?
Не полностью. Убрать поле из новых событий не значит убрать его из профилей пользователей, куда оно уже записано. Для этого нужен проход $unset через Identify API по существующей базе: в проекте для банковского суперприложения это было порядка 50 000 пользователей, и планирование пропускной способности было отдельной строкой работ. Blocked properties в схеме событий и CI-линтер не дают полю вернуться.
Узнаёте свою воронку?
Две недели, фиксированный скоуп, доступы только на чтение. Находки и роадмап на 90 дней остаются у вас в любом случае.
