Если Google Ads видит лиды, но не видит выдач, причина почти всегда одна: gclid теряется где-то между кликом и серверным денежным событием. На кредитном PWA-агрегаторе мы нашли два разрыва: 302 редирект-трекера прокидывал свой clickid, но терял gclid, а бэкенд сохранял gclid, но не читал его при отправке purchase. Починка: прокинуть его через трекер и отправить выдачу серверным purchase с приложенным gclid.
Симптом, который приводит к этому разбору, звучит одинаково в любом лидогенерационном проекте: в Google Ads видно лиды - заявки идут, CPL приемлемый, - но целевого действия, которое приносит деньги, платформа не видит вовсе. В кредитном продукте это выдача займа, в подписке - оплата, в e-commerce - выкуп.
Разберём это на воронке кредитного PWA-агрегатора: пользователь приходит по Google Ads, оставляет заявку, а выдача (деньги) фиксируется позже - постбэком от кредитора на бэкенд, без участия браузера. Между кликом и лендингом стоит редирект-трекер (класс инструментов вроде Keitaro/Binom). Именно в этой связке gclid и терялся.
Что такое цепочка gclid и где она рвётся
gclid (Google Click Identifier) - это токен, который Google Ads с включённым авто-тегированием дописывает к финальному URL объявления. Вся атрибуция Google построена на нём: без gclid в сессии Google Ads не понимает, что этот визит - от его клика, а серверная конверсия, случившаяся через два дня, не привязывается к кампании.
Теперь наложим редирект-трекер. Финальный URL объявления ведёт не на лендинг, а на трекер: https://track.example/<token>. Трекер отдаёт 302 и уводит пользователя на лендинг: https://app.example/?clickid=<subid>. И вот здесь ловушка: на редиректе трекер прокидывает свой clickid, но не прокидывает gclid. В результате:

Google Ads (auto-tagging)

track.example/<token>?gclid=ABC…
gclid ещё здесь

302

app.example/?clickid=xyz
gclid ПОТЕРЯН
Лендинг открывается без gclid. Дальше по цепочке всё выглядит нормально: заявка создаётся, clickid трекера едет в бэкенд, лиды считаются. Но GA4-сессия не помечена как Google Ads / paid, потому что в URL не было gclid. Когда приходит выдача - серверное событие purchase - привязать его к клику Google уже нечем. Лиды есть (их держит clickid трекера), выдач нет (для них нужен gclid, а он умер на 302).
Второй, тихий баг: gclid писали, но не читали
Даже там, где gclid удавалось поймать на сабмите, вскрылась вторая проблема - уже не в трекере, а в бэкенде. При сохранении заявки gclid записывался в БД, но функция, которая потом доставала заявку для отправки конверсии, этот gclid не читала. Классический скрытый пробел: колонка есть, значение пишется, но на этапе отправки purchase его никто не подставляет.
Наружу это выглядело точно так же - «выдачи не атрибутируются», - хотя данные в базе лежали. Такие баги не видны в дашбордах: их находят, только пройдя путь значения от захвата до отправки поле за полем.
Как замкнуть цепочку
Починка - это не один тумблер, а восстановление сквозного пути gclid от клика до денежного события.
1. Прокинуть gclid через редирект-трекера. В offer/flow URL трекера добавляется gclid, чтобы он пережил 302 вместе с clickid:
{gclid} - валидный макрос источника Google Ads внутри трекера. Тест-клик после правки должен показать на лендинге оба параметра. Важная деталь про комплаенс: parallel tracking ограничивает только tracking template, но не Final URL-редирект. Поэтому вести финальный URL на трекер (на поддомене того же корневого домена, что и лендинг) - допустимо, атрибуция не ломается.
2. Захватить gclid и client_id на лендинге и сохранить вместе с заявкой. Параллельно снимается _ga (GA4 client_id) - он понадобится, чтобы серверный purchase попал в ту же сессию.
3. На выдаче отправить server-side purchase в GA4 через Measurement Protocol: событие purchase с value (наша комиссия), transaction_id = pb_<id>, client_id из cookie _ga, плюс gclid и хешированные email/телефон для Enhanced Conversions. Выдача приходит постбэком, без браузера - поэтому фронтовый GTM её не поймает, нужен серверный хит.
4. Импортировать purchase в Google Ads. purchase делается Key event в GA4 и импортируется в Google Ads (GA4 → Ads import). Дальше кампании оптимизируются на выдачу, а не на дешёвый лид. Одно правило: не задваивать - либо GA4 → Ads import, либо прямой upload в Ads API, но не оба сразу, иначе конверсии удвоятся.
Дедуп по всей цепочке держится на transaction_id = pb_<id>.
Подводные камни при деплое
Теория выше - ровная. На реальном сервере всплыли вынужденные отклонения, которые полезно знать заранее.
_ga пришлось читать серверно, а не на фронте. На боксе лежал только собранный dist, исходников фронта не было - пересобрать и добавить фронтовый захват client_id невозможно. Решение: cookie _ga едет с same-origin POST-запросом заявки, и бэкенд читает client_id прямо из req.headers.cookie. Ребилд фронта не потребовался.
Measurement Protocol - напрямую в Google, а не через sGTM. Интуитивно хочется пустить серверный хит через свой server-side GTM. Но сток-контейнер sGTM с GA4-клиентом отдаёт 400 на /mp/collect - GA4-клиент слушает /g/collect, а не MP-эндпоинт. А для чистого server-to-server хита у sGTM и нет first-party-выгоды (нет браузера, нет блокировщиков). Поэтому purchase уходит прямо на https://www.google-analytics.com/mp/collect (ответ 204 = принято).
Cloudflare Bot Fight Mode режет server-to-server POST. Если домен проксирован через Cloudflare, Bot Fight Mode может рубить серверные POST-запросы к /mp/collect. Нужен WAF-skip для этого пути (или вести MP мимо прокси).
Захват client_id не стопроцентный - и это нормально. На живых сабмитах _ga ловится примерно у 90-92%; пропуски - адблокеры, режущие GA-cookie. Это ожидаемый уровень, а не повод чинить.
Главный вывод
«Лиды есть, конверсий/выручки нет» в Google Ads - это почти никогда не про алгоритм и почти всегда про порванную цепочку click id. Проследите gclid от объявления через все редиректы до лендинга и дальше до серверного денежного события - буквально глазами, поле за полем, тест-кликом. Точка разрыва обычно одна: 302 редирект-трекера, забытое чтение поля в бэкенде, или отсутствие серверного purchase-хита на моменте выдачи.
И более общий принцип: атрибуция не закончена в момент, когда клик размечен - она закончена, когда денежное событие донесло click id обратно. Пока purchase не несёт gclid (и его аналоги в других каналах), Google оптимизируется на самый дешёвый лид, а не на платящего клиента, - и делает это ровно так хорошо, как вы его об этом попросили.
Частые вопросы
В Google Ads есть лиды, но нет выручки. В чем обычно причина?
В разорванной цепочке gclid. Чаще всего ее рвет редирект-трекер в середине пути: click ID теряется до того, как его кто-то сохранит.
gclid есть в базе - значит, цепочка работает?
Не обязательно. Второй баг в этом кейсе был именно таким: gclid записывался и никогда не читался. Колонка заполнена, а при отправке конверсии ее никто не использует.
Как восстановить цепочку?
Восстановить весь путь gclid. Добавить gclid={gclid} в offer URL трекера, чтобы он пережил 302, сохранить gclid и client_id из _ga вместе с заявкой, на выдаче отправить server-side purchase в GA4 через Measurement Protocol и импортировать его в Google Ads. Использовать либо импорт из GA4, либо прямой upload в Ads API, но не оба сразу.
Узнаёте свою воронку?
Две недели, фиксированный скоуп, доступы только на чтение. Находки и роадмап на 90 дней остаются у вас в любом случае.
