Сквозная атрибуция для двух Shopify-магазинов: от клика до заказа
Бренд товаров для активного отдыха из США платил за Meta, Google и YouTube, но не знал, какая реклама приносит заказы. Собрал контур «рекламный клик → заказ → рекламный кабинет» на трекере Keitaro и собственном промежуточном сервисе. Отчёты по выручке перестали расходиться с реальностью, а владелец получил основание перераспределить рекламный бюджет.
Три источника цифр, и ни один не сходится с другими
Бизнес тратил деньги на Meta, Google и YouTube — и не мог ответить на первый же вопрос:
какая реклама приносит заказы. Витрина Shopify списывала большую долю продаж на
Direct,
рекламные кабинеты рапортовали конверсии, которые не сходились с оперативной таблицей
владельца, а сама таблица не бьётся ни с тем, ни с другим.
Владелец бизнеса формулировал это прямо: «данные не совпадают». И это худший вариант из возможных — не отсутствие аналитики, а аналитика, которой не доверяют. Когда три отчёта дают три разные выручки, дальше начинается спор о цифрах, а не разговор о рекламе.
Практическое следствие: решения по бюджету принимались на ощущениях. Никто не мог сказать, сколько стоит одна продажа по каждому каналу, — а значит, нельзя было ни выключить убыточное плечо, ни осознанно долить в прибыльное. Деньги распределялись по привычке.
Непрерывный путь данных: клик не теряется нигде
Атрибуция ломается не в одном месте, а в четырёх: метка клика не долетает до витрины, вебхук приходит дважды, серверное событие задваивает браузерное, расход не попадает в отчёт. Поэтому строился не «пиксель», а сплошной контур — каждое звено проверяемо отдельно.
Метка клика снимается прямо в шаблоне магазина и дублируется собственным идентификатором
Корзина, оформление и заказ уходят в промежуточный сервис на http.server: проверка подписи по каждому магазину, отсев повторов
С сервера на сервер: обращение — на уникальный клик, продажа с суммой — на заказ
В SQLite сходятся идентификатор клика, хеш почты и номер оформления — заказ привязывается к первому клику задним числом
Весь путь клиента по касаниям, вклад и роль каждого канала, доход с клиента за всё время, «победы атрибуции»
Метку клика пришлось снимать в самой теме
Штатный путь — Web Pixel — не сработал: в песочнице Shopify он не пишет
cart attributes,
то есть метка клика просто не доезжает до заказа. Пришлось ловить её в самой теме магазина
и дублировать собственным идентификатором визита
(vid),
чтобы связь клик↔заказ держалась даже там, где штатный механизм её теряет.
Middleware: HMAC по каждому магазину, дедупликация на входе
Собственный промежуточный сервис на
http.server
принимает уведомления Shopify и делает четыре вещи:
- проверяет
HMAC-подпись — отдельным секретом для каждого из двух магазинов - дедуплицирует события: Shopify повторяет доставку, отчёт от этого не должен расти
- передаёт данные с сервера в трекер:
lead— обращение на каждый уникальный клик,sale— продажа с суммой на заказ - пишет каждое событие воронки в собственный
JSONL— гранулярный путь сохраняется там, где трекер уже только агрегирует
Conversions API: события схлопываются, а не задваиваются
Одновременно с передачей в трекер сервис отправляет с сервера
Purchase
в Facebook Conversions API — с тем же
event_id,
что и браузерный пиксель. Это ключевая деталь: без общего идентификатора вы получаете не
восстановленную атрибуцию, а удвоенные конверсии в кабинете и оптимизацию по выдуманным
цифрам.
Identity-граф вместо last-click
Отдельная база SQLite склеивает клик, покупателя и заказ по
ksubid,
хешу e-mail и
checkout_token,
с обратной привязкой к первому клику. Поверх этого графа — слой отчётов, который отвечает
на вопросы, на которые нельзя ответить, если учитывать только последний клик:
- мультитач-путь покупателя и распределение заслуги между каналами
- роль канала: открывает / догоняет / закрывает
- LTV по каналу — а не разовая выручка первого заказа
- «победы атрибуции»: витрина сказала
Direct, трекер поймал платный источник - сшивка с post-purchase опросом «откуда узнали» — независимая проверка модели
Расход выгружается ежедневно — иначе окупаемость считается без затрат
Половина проектов по атрибуции ломается на банальном: выручка по каналу есть, а расход остался в кабинете. Расходы по кампаниям тянутся по расписанию из Google Ads API и Meta Marketing API прямо в трекер, поэтому цена продажи по каналу — не ручная сверка раз в месяц, а поле в отчёте.
Только стандартная библиотека
Весь контур написан на
urllib,
http.server
и
sqlite3
— без единой внешней зависимости. Причина прозаическая: код обязан работать на боевом
сервере, где нет pip.
Побочный эффект — нечему устаревать и нечего обновлять по сообщениям об уязвимостях.
Ноль зависимостей — потому что боевой сервер без pip
http.server, urllib, sqlite3 — ни одной внешней зависимости
Приложение с авторизацией, уведомления о корзине, оформлении и заказе, проверка подписи по каждому магазину
Приём событий с сервера (обращение и продажа) и выгрузка кампаний и расходов
Покупка передаётся с сервера с тем же идентификатором события — задвоения с пикселем в браузере исключены
Ежедневная синхронизация расхода по кампаниям в трекер
Перекрёстная проверка сессий и источников против собственных данных
Связка «клик ↔ клиент ↔ заказ» и привязка к первому клику задним числом
Метка клика снимается в шаблоне магазина, плюс собственный идентификатор
Сервис работает как служба, обмен данными по расписанию, перезапуск без ручных действий
Подробная воронка в собственном хранилище, независимо от сводных цифр трекера
Отчёт перестал врать — и сразу изменил решения
фантомная выручка ушла после починки расчёта
отменённые заказы в выручке (+25,7%) и невычтенные промокоды (+5,74%)
веб-выручки одного магазина — причина найдена и подтверждена
Два дефекта расчёта, которые ломали доверие к отчёту
Первое, что дал контур, — возможность сверить отчёт с реальностью, и сверка сразу нашла две ошибки. Отменённые заказы попадали в выручку: на одном дне это давало завышение на 25,7% и, что хуже, выставляло отменённый заказ «победой атрибуции». Промокоды не вычитались — ещё 5,74% сверху. Оба дефекта закрыты; дневной отчёт перестал расходиться с реальной выручкой.
Цена продажи по каналу изменила решение по бюджету
Когда расход по кампаниям и отслеженные продажи сошлись в одном отчёте, картина оказалась неприятно чёткой: при ~186 тыс. кликов с Facebook — ни одной отслеженной продажи, тогда как все отслеженные продажи месяца пришли с YouTube при ~12 тыс. кликов. Владелец бизнеса на этих данных выключил FB-плечо и перераспределил бюджет. Это ровно та ситуация, ради которой строится атрибуция: не «красивая панель с графиками», а одно управленческое решение, которое без цифр не принимается.
Треть выручки без источника оказалась не багом трекинга
Около трети веб-выручки одного из магазинов приходила без источника — и правильный ответ здесь был не «чинить пиксель». Причина оказалась внешней: баннер согласия на куки блокировал сессию Shopify до нажатия кнопки согласия, то есть источник терялся ещё до того, как до него доходила очередь у аналитики. Диагноз подтверждён A/B-сравнением двух витрин и историческим прецедентом второго магазина — а не догадкой.
Общий итог, если убрать детали: у бизнеса появился один источник правды, который выдерживает проверку. Отчёту снова можно задавать вопросы — и получать ответ, который не приходится перепроверять вручную.
Где ещё ложится та же методология
Это кейс не про Shopify. Это типовая задача «склеить событие на сайте с деньгами в кассе и с расходом в рекламном кабинете». Контур переносится почти без изменений везде, где есть платный трафик и заказы:
- → Любой магазин на Shopify / WooCommerce / Tilda, где витрина показывает подозрительно много «Direct»
- → Несколько витрин / доменов на общем рекламном бюджете — единая модель атрибуции вместо разрозненных кабинетов
- → Лидовые бизнесы с длинным циклом — заявка, звонок, сделка в CRM: та же цепочка «клик → идентификатор → передача данных с сервера»
- → Server-side события для Meta / Google / TikTok после потерь браузерного трекинга — с обязательной дедупликацией по event_id
- → Аудит существующей аналитики, когда отчёты уже есть, но расходятся между собой — часто это дефект расчёта, а не трекинга
- Шаблон промежуточного сервиса на стандартной библиотеке: приём уведомлений, проверка подписи по магазину, отсев повторов, передача события с сервера
- Общий идентификатор события для пикселя в браузере и Conversions API — конверсии не задваиваются
- Связка на SQLite: клик ↔ клиент ↔ заказ, с привязкой к первому касанию задним числом
- Ежедневная выгрузка расходов из рекламных кабинетов — окупаемость считается с затратами, а не без них
- Набор отчётов поверх трекера: весь путь по касаниям, вклад и роль канала, доход с клиента за всё время, «победы атрибуции»
Если витрина показывает «Direct», а отчёты не сходятся между собой — это чинится
Начинать имеет смысл не со строительства, а с аудита контура: где именно теряется метка, где задваивается событие и где отчёт считает не то, что показывает. Ядро атрибуции собирается за 2–3 недели, дальше — отчёты и отладка на реальных данных.
Похожие кейсы
Оплаченные заказы Shopify → рабочие таблицы склада
Заказы, номера отправлений, статусы и отмены проставляются сами каждые 30 минут. Приёмник на Advanced Sheets…
Разбор компании по открытым источникам: что видно снаружи
Методика из 12 шагов на бесплатных источниках (WHOIS, DNS, SSL/crt.sh, URLscan, Wayback) плюс собственные…
Авто-сборка еженедельных отчётов клиентам
Сценарий в n8n на 8 клиентов: собирает цифры со всех площадок, AI-комментарий, отчёт в PDF или Notion уходит…
Аудит за 5 000 ₽ — с конкретным отчётом и сметой
Расскажу что внедрить в вашем бизнесе в первую очередь, какая будет окупаемость, и нужен ли вообще AI для вашей задачи (иногда — нет).
Или просто напишите свой вопрос — отвечу в течение 2 часов