Перейти к содержимому
VC
Кейс № 19 · B2B / E-commerce · Аналитика

Сквозная атрибуция для двух Shopify-магазинов: от клика до заказа

US-бренд товаров для активного отдыха платил за Meta, Google и YouTube, но не знал, какая реклама приносит заказы. Собрал контур «рекламный клик → заказ → рекламный кабинет» на трекере Keitaro и собственном middleware. Отчёты по выручке перестали расходиться с реальностью, а владелец получил основание перераспределить рекламный бюджет.

Индустрия
E-commerce · два магазина на Shopify
Стек
Python (stdlib) · Shopify API · Keitaro · FB CAPI
Сроки
Ядро — 3 недели, развитие — 2 месяца
Итог
+25,7% фантомной выручки → 0
01 · Боль

Три источника цифр, и ни один не сходится с другими

Бизнес тратил деньги на Meta, Google и YouTube — и не мог ответить на первый же вопрос: какая реклама приносит заказы. Витрина Shopify списывала большую долю продаж на Direct, рекламные кабинеты рапортовали конверсии, которые не сходились с оперативной таблицей владельца, а сама таблица не бьётся ни с тем, ни с другим.

Владелец бизнеса формулировал это прямо: «данные не совпадают». И это худший вариант из возможных — не отсутствие аналитики, а аналитика, которой не доверяют. Когда три отчёта дают три разные выручки, дальше начинается спор о цифрах, а не разговор о рекламе.

Практическое следствие: решения по бюджету принимались на ощущениях. Никто не мог сказать, сколько стоит одна продажа по каждому каналу, — а значит, нельзя было ни выключить убыточное плечо, ни осознанно долить в прибыльное. Деньги распределялись по привычке.

02 · Решение

Непрерывный путь данных: клик не теряется нигде

Атрибуция ломается не в одном месте, а в четырёх: метка клика не долетает до витрины, вебхук приходит дважды, серверное событие задваивает браузерное, расход не попадает в отчёт. Поэтому строился не «пиксель», а сплошной контур — каждое звено проверяемо отдельно.

01
Клик

Метка клика снимается в теме магазина + дублируется собственным vid

02
Вебхуки

cart / checkout / order → middleware на http.server, HMAC по каждому магазину, дедупликация

03
Постбэки

S2S в трекер: lead на уникальный клик, sale с выручкой на заказ

04
Identity

SQLite-граф: ksubid / хеш e-mail / checkout_token → обратная привязка к первому клику

05
Отчёты

Мультитач-путь, вклад и роль канала, 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-алертам.

03 · Стек

Ноль зависимостей — потому что боевой сервер без pip

Python (stdlib-only)

http.server, urllib, sqlite3 — ни одной внешней зависимости

Shopify Admin API + Webhooks

OAuth-приложение, вебхуки cart / checkout / order, HMAC по каждому магазину

Keitaro

S2S-постбэки (lead / sale) и Admin API для кампаний и расхода

Facebook Conversions API

Server-side Purchase с общим event_id — дедупликация с браузерным пикселем

Google Ads API · Meta Marketing API

Ежедневная синхронизация расхода по кампаниям в трекер

GA4 Data API

Кросс-проверка сессий и источников против собственных данных

SQLite

Identity-граф клик↔клиент↔заказ + обратная привязка к первому клику

Shopify Liquid / Web Pixel

Съём метки клика в теме магазина + собственный vid

systemd + nginx

Middleware как сервис, синхронизации по таймерам, перезапуск без ручных действий

JSONL-журнал событий

Гранулярная воронка в собственном хранилище, независимо от агрегатов трекера

Python stdlibShopify APIWebhooks + HMACKeitaro S2SFacebook CAPIGoogle Ads APIMeta Marketing APIGA4 Data APISQLiteLiquidsystemdnginx
04 · Результат

Отчёт перестал врать — и сразу изменил решения

Расхождение дневного отчёта
+25,7% 0

фантомная выручка ушла после починки расчёта

Дефекты расчёта
2

отменённые заказы в выручке (+25,7%) и невычтенные промокоды (+5,74%)

Выручка без источника
≈ ⅓

веб-выручки одного магазина — причина найдена и подтверждена

Два дефекта расчёта, которые ломали доверие к отчёту

Первое, что дал контур, — возможность сверить отчёт с реальностью, и сверка сразу нашла две ошибки. Отменённые заказы попадали в выручку: на одном дне это давало завышение на 25,7% и, что хуже, выставляло отменённый заказ «победой атрибуции». Промокоды не вычитались — ещё 5,74% сверху. Оба дефекта закрыты; дневной отчёт перестал расходиться с реальной выручкой.

Цена продажи по каналу изменила решение по бюджету

Когда расход по кампаниям и отслеженные продажи сошлись в одном отчёте, картина оказалась неприятно чёткой: при ~186 тыс. кликов с Facebook — ни одной отслеженной продажи, тогда как все отслеженные продажи месяца пришли с YouTube при ~12 тыс. кликов. Владелец бизнеса на этих данных выключил FB-плечо и перераспределил бюджет. Это ровно та ситуация, ради которой строится атрибуция: не «красивый дашборд», а одно управленческое решение, которое без цифр не принимается.

Треть выручки без источника оказалась не багом трекинга

Около трети веб-выручки одного из магазинов приходила без источника — и правильный ответ здесь был не «чинить пиксель». Причина оказалась внешней: баннер согласия на куки блокировал сессию Shopify до нажатия «Accept», то есть источник терялся ещё до того, как до него доходила очередь у аналитики. Диагноз подтверждён A/B-сравнением двух витрин и историческим прецедентом второго магазина — а не догадкой.

Общий итог, если убрать детали: у бизнеса появился один источник правды, который выдерживает проверку. Отчёту снова можно задавать вопросы — и получать ответ, который не приходится перепроверять вручную.

05 · Применимость

Где ещё ложится та же методология

Это кейс не про 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 часов