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