Diil Docs
  1. Документация
  2. Виджет

События и аналитика: просмотры, свои события, конверсии

Обновлено:

Тег widget.js, который вы уже поставили ради живого редактирования, втихую считает каждый просмотр, понимает, откуда пришёл посетитель и какая реклама его привела. Добавьте одну строку JavaScript — или один запрос с сервера, — и вы увидите ещё и тех, кто положил товар в корзину, зарегистрировался и наконец заплатил. Вместе с суммой. Без второго счётчика, без менеджера тегов и без переписывания cookie-баннера.

Что в меню:

  • Просмотры страниц — сами, без единой строки кода. Каналы, UTM-метки, идентификаторы кликов, география, устройства.
  • Свои события из браузера — window.crmTrack('add_to_cart', …) для поведения на сайте.
  • События с сервера — POST /marketing/event с секретным ключом: для покупок и всего, что нужно посчитать ровно один раз.

Всё это оказывается в CRM в разделе Маркетинг: визиты — в Посещаемости, ваши события — в Событиях, размеченные ссылки — в Рекламных каналах.

Что работает сразу: просмотры страниц

Если на странице есть widget.js, просмотры уже считаются. Тег тот же, что и в разделе Виджет и разметка:

<script src="https://widget.sitecog.com/widget.js" defer></script>

При каждой загрузке страницы виджет отправляет один просмотр через navigator.sendBeacon — крошечный запрос «отправил и забыл»: страницу он не тормозит и доходит, даже если посетитель тут же закрыл вкладку. Сервер отвечает 204 No Content — читать в ответе нечего.

Что собирается

Из браузера:

  • адрес страницы, заголовок и реферер;
  • UTM-метки: utm_source, utm_medium, utm_campaign, utm_term, utm_content;
  • идентификаторы рекламных кликов: gclid, gbraid, wbraid (Google), yclid (Яндекс), fbclid (Meta), msclkid (Microsoft);
  • код рекламной ссылки ?dl= из Рекламных каналов;
  • несколько рекламных cookie, если они на сайте уже есть: _ga, _gcl_*, _ym_uid, _fbp, _fbc. В режиме согласия они не читаются, пока посетитель не согласился, а если браузер передаёт Global Privacy Control — не читаются вообще никогда;
  • размер экрана и окна, плотность пикселей, язык браузера и часовой пояс.

Добавляется на нашей стороне:

  • местоположение — по IP-адресу;
  • тип устройства, браузер и операционная система;
  • канал трафика: платный, соцсети, поиск, переходы с сайтов, почта или прямые заходы;
  • новый это посетитель или вернувшийся.

Сам IP-адрес хранится усечённым, а из адресов страниц и рефереров перед сохранением вычищаются якорь #… и параметры вроде токенов и паролей. Что именно и сколько живёт — в разделе Что мы храним и сколько.

Ботов мы не выбрасываем, а помечаем и убираем из отчётов. Так что цифры в отчётах — про людей, а сырые данные остаются: вдруг захочется узнать, какая доля трафика на самом деле краулеры (спойлер: больше, чем хотелось бы).

Отчёты — в Маркетинг → Посещаемость: посетители, сессии, каналы, страницы, география и устройства.

Сутки и часы в отчётах

«Вчера» в отчёте — это вчера в часовом поясе вашего сайта, а не по UTC и не в поясе того, кто сейчас смотрит на график. Магазин в Киеве видит вечерний пик в 20:00, а не в 17:00, а владелец в Киеве и менеджер в Нью-Йорке смотрят на одни и те же сутки.

  • Пояс задаётся в CRM → Настройки → Часовой пояс. Пока его никто не выбрал, CRM берёт часовой пояс браузера владельца.
  • По нему режутся сутки в Посещаемости, Рекламных каналах и Событиях и считаются часы на графиках.
  • Смена пояса пересчитывает и прошлые отчёты. Сами данные не меняются — меняется только то, где проходит полночь.

Идентификаторы посетителя и сессии

Вернувшегося посетителя виджет узнаёт без cookie. Он хранит три ключа в localStorage:

КлючЧто этоСколько живёт
crm_vidИдентификатор посетителяПока посетитель не очистит данные сайта
crm_sidИдентификатор сессииНовая сессия начинается после 30 минут бездействия
crm_satВремя последней активности — по нему виджет решает, что сессия закончиласьОбновляется, пока посетитель ходит по сайту

Если хранилище заблокировано (так делают некоторые приватные режимы), идентификаторы живут в памяти, пока открыта вкладка. Запомните crm_vid и crm_sid — они понадобятся, чтобы привязать серверные события к посетителю. В режиме согласия эти ключи появляются только после согласия, а crmConsent.deny() их стирает.

Одностраничные приложения

В SPA (React Router, клиентская навигация Next.js, Vue Router) страница не перезагружается, а просмотры всё равно считаются — сами, без единой строки кода. Виджет следит за history.pushState, history.replaceState и событием popstate и, выждав 300 мс (чтобы цепочка быстрых переходов и редиректов не превратилась в пачку просмотров), отправляет просмотр, если поменялся путь или строка запроса. Реферером такого «виртуального» просмотра становится предыдущая страница вашего сайта — так что пути посетителя по сайту в отчётах выглядят так же, как на обычном многостраничном сайте.

Поведение настраивается атрибутом data-spa на теге скрипта:

ЗначениеЧто считается просмотром
атрибута нетЗагрузка страницы плюс каждая смена пути или строки запроса. Подходит большинству SPA.
data-spa="hash"То же самое плюс смена якоря — для приложений с маршрутами вида #/catalog.
data-spa="off"Отслеживание SPA выключено: ровно один просмотр на одну загрузку скрипта, как раньше.
Приложение с маршрутами на якоряхhtml
<script src="https://widget.sitecog.com/widget.js" data-spa="hash" defer></script>

Приватность и согласие

Cookie виджет не ставит, но аналитика остаётся аналитикой. Если сайту нужно согласие посетителя, включите режим согласия: виджет будет молчать, пока ваш баннер не скажет «можно». Переписывать баннер не придётся — достаточно, чтобы его кнопки дёргали одну функцию.

Включается одним атрибутом на теге:

<script src="https://widget.sitecog.com/widget.js" data-consent="required" defer></script>

Пока посетитель не согласился, виджет:

  • не отправляет ни просмотров, ни событий. Они именно выбрасываются, а не копятся в очереди: задним числом после согласия ничего не уйдёт;
  • не читает document.cookie — рекламные _ga, _gcl_*, _ym_uid, _fbp, _fbc остаются нетронутыми;
  • не создаёт и не хранит crm_vid, crm_sid и crm_sat.

Без атрибута всё работает как раньше: счёт начинается сразу, с первой загрузки.

Заявки и чат поддержки работают и до согласия — закрывать посетителю дорогу к вам незачем. Но идентификаторов посетителя и сессии у них в этот момент нет, а значит, нет и атрибуции: такая заявка придёт без канала, UTM-меток и рекламной ссылки. Чат до согласия пользуется одноразовым идентификатором, который живёт только в памяти и никуда не сохраняется (старый разговор при этом по-прежнему восстанавливается по своему crm_chat_token).

Виджет кладёт в window объект crmConsent:

crmConsent.grant()functionнеобязательно
Посетитель согласился. Виджет начинает считать и сразу отправляет один просмотр текущей страницы — визит, на котором человек нажал «Принять», не теряется.
crmConsent.deny()functionнеобязательно
Посетитель отказался. Стирает crm_vid, crm_sid и crm_sat. Работает даже без data-consent — см. ниже.
crmConsent.status()() => 'granted' | 'denied' | 'pending'необязательно
Текущий выбор: согласился, отказался или ещё не ответил.
crmConsent.requiredbooleanнеобязательно
true, если на теге стоит data-consent="required".
crmConsent.gpcbooleanнеобязательно
true, если браузер передаёт Global Privacy Control.

Выбор запоминается в localStorage под ключом crm_consent, так что при следующих визитах баннер можно не показывать. А когда выбор меняется, на window приходит событие crm:consent — в обработчике просто спросите window.crmConsent.status().

Баннер нередко загружается раньше виджета. На этот случай есть очередь — та же идея, что и у crmq:

Работает и до, и после загрузки widget.jsjs
window.crmConsent = window.crmConsent || [];
crmConsent.push('grant');   // или 'deny'

Загрузившись, виджет выполнит всё, что накопилось, а дальше push срабатывает сразу. Так что кнопкам баннера проще всего всегда пользоваться push и не думать о порядке загрузки.

<script src="https://widget.sitecog.com/widget.js" data-consent="required" defer></script>

<div id="consent-banner" hidden>
  <p>Мы считаем посещения, чтобы понимать, какая реклама работает. Можно?</p>
  <button type="button" data-choice="grant">Принять</button>
  <button type="button" data-choice="deny">Отказаться</button>
</div>

<script>
  window.crmConsent = window.crmConsent || [];
  const banner = document.getElementById('consent-banner');

  banner.addEventListener('click', (e) => {
    const choice = e.target.closest('[data-choice]')?.dataset.choice;
    if (!choice) return;
    crmConsent.push(choice); // 'grant' или 'deny'
    banner.hidden = true;
  });

  // widget.js с defer выполняется до DOMContentLoaded, так что status() уже на месте.
  // Выбор сделан в прошлый раз — баннер не показываем. Нет виджета (блокировщик) — тоже.
  document.addEventListener('DOMContentLoaded', () => {
    if (window.crmConsent.status?.() === 'pending') banner.hidden = false;
  });
</script>

Global Privacy Control и отказ без баннера

Некоторые браузеры и расширения передают сигнал Global Privacy Control (navigator.globalPrivacyControl) — «не продавайте и не передавайте мои данные». Если он есть, виджет никогда не читает рекламные cookie — в любом режиме, с атрибутом data-consent или без. Проверить сигнал из своего кода можно через window.crmConsent.gpc.

deny() тоже работает на любом сайте, даже без data-consent: отказ соблюдается, а идентификаторы стираются. Так что если баннера у вас нет и не будет, честную ссылку «Не отслеживать меня» в подвале всё равно можно сделать за пять строк:

Ссылка в подвалеhtml
<a href="#" id="do-not-track">Не отслеживать меня</a>

<script>
  document.getElementById('do-not-track').addEventListener('click', (e) => {
    e.preventDefault();
    window.crmConsent = window.crmConsent || [];
    crmConsent.push('deny');
    e.currentTarget.textContent = 'Готово: больше не отслеживаем';
  });
</script>

Что мы храним и сколько

IP-адреса хранятся усечёнными. У IPv4 обнуляется последний октет (203.0.113.57 → 203.0.113.0), IPv6 обрезается до /48. Страну и город мы определяем по полному адресу ещё до усечения, поэтому отчёты по географии работают как прежде.

Адреса чистятся перед сохранением. Из адресов страниц, рефереров, адреса первой страницы визита и адресов событий убираются якорь (#…) и чувствительные параметры запроса:

token *_token access_token id_token refresh_token auth_token auth password pass passwd pwd email e-mail mail key api_key apikey secret client_secret otp session sessionid jwt

*_token — это любое имя, которое заканчивается на _token. А вот code остаётся: промокоды в ссылках — обычное дело. UTM-метки, идентификаторы кликов и dl тоже на месте, иначе атрибуции было бы не из чего взяться.

Просмотры и события живут 13 месяцев (395 дней), потом удаляются. Срок настраивается на стороне сервера — его может поменять тот, кто администрирует вашу установку Diil. Заявки под это правило не попадают и не удаляются.

Посетитель может попросить забыть его раньше. Владелец сайта удаляет его профиль, просмотры и события по идентификатору посетителя прямо из CRM; заявки — только если это выбрано отдельно. Подробнее — в разделе Заявки → Удаление данных человека.

В Маркетинг → Рекламные каналы вы создаёте размеченную ссылку для каждой площадки — пост в соцсети, рассылка, баннер на чужом сайте. Ссылка ведёт на ваш сайт и несёт UTM-метки плюс короткий код:

https://shop.example/?utm_source=instagram&utm_medium=social&utm_campaign=autumn_sale&dl=k3Zp9QaW1x

dl — код ссылки из 10 символов. На сайте ничего делать не нужно: виджет подхватит его с первым просмотром, и все визиты, события и заявки этой сессии засчитаются ссылке. У каждой ссылки в CRM своя статистика — видно, сколько людей она привела.

Свои события

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

Шаг ноль: опишите событие в CRM

Diil принимает только знакомые события. И это фича: опечатка в коде не создаст молча новый тип события и не расколет ваши отчёты надвое.

  1. Откройте Маркетинг → События и нажмите «Создать событие»

    Вверху должен быть выбран сайт (домен).
  2. Назовите его так же, как в коде

    add_to_cart, signup_completed, purchase. Латиница, цифры и подчёркивания, начинается с буквы, до 64 символов. Добавьте человеческое название и описание — вы же через полгода скажете себе спасибо.
  3. Опишите параметры

    До 20 на событие, у каждого тип: Строка, Число или Да/нет. Всё, что здесь не описано, в базу не попадёт.
  4. Про деньги? Отметьте «Событие приносит деньги»

    И укажите название валюты (EUR, USD, USDT или даже ваши бонусные баллы). Без этой галочки value события не сохраняется.
  5. Скопируйте готовый вызов

    CRM покажет точный вызов crmTrack и серверный запрос именно для этого события. Вставили — работает.

На сайте может быть до 100 активных типов событий. Когда по событию уже пришли данные, его имя меняться не может (оно ведь уже в коде), а удалить событие нельзя — только отправить в архив, чтобы в отчётах не остались строки без названия.

События из браузера: crmTrack

Сигнатураts
window.crmTrack(
  name: string,
  params?: Record<string, string | number | boolean>,
  options?: { id?: string; value?: number; currency?: string },
): void

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

Аргументы

namestringобязательно
Имя события, как оно описано в CRM. Латиница, цифры и подчёркивания, начинается с буквы, до 64 символов. Неописанное имя молча отбрасывается — см. Если что-то не так.
paramsobjectнеобязательноПо умолчанию: {}
Плоский объект с подробностями: { sku: 'air3-graphite', price: 149 }. Ключи — по тем же правилам, что и имя, до 40 символов. Сохраняются только параметры, описанные в CRM; значения приводятся к объявленному типу — см. Параметры и типы.
options.idstringнеобязательноПо умолчанию: нет
Ключ идемпотентности, до 128 символов, уникален в пределах сайта. Отправили один id дважды — событие сохранится один раз. Для покупок берите номер заказа.
options.valueintegerнеобязательноПо умолчанию: нет
Сумма в минимальных единицах: 149900 — это 1499.00. От 0 до 1012. Сохраняется, только если у события включено «Событие приносит деньги».
options.currencystringнеобязательноПо умолчанию: валюта события
Обозначение валюты, 1–10 латинских букв или цифр: EUR, USD, UAH. Не передали — возьмётся валюта, указанная у события в CRM.

Примеры

Три события, без которых не обходится почти ни один магазин, — на чём бы ни был сделан ваш сайт:

<button id="buy" data-sku="air3-graphite" data-price="149">В корзину</button>

<form id="signup">…</form>

<script>
  // ?. — чтобы блокировщик рекламы, съевший widget.js, не сломал кнопку
  document.getElementById('buy').addEventListener('click', (e) => {
    const { sku, price } = e.currentTarget.dataset;
    window.crmTrack?.('add_to_cart', { sku, price: Number(price) });
  });

  // Вызывайте, когда аккаунт действительно создан, а не на первом клике
  function onSignupSuccess() {
    window.crmTrack?.('signup_completed', { method: 'email', newsletter: true });
  }

  // На странице «Спасибо за заказ». Этот код выполняется раньше отложенного widget.js,
  // поэтому идёт через очередь (см. ниже); id делает перезагрузку безвредной
  window.crmq = window.crmq || [];
  crmq.push([
    'purchase',
    { order_id: 'A-1024', items: 2 },
    { id: 'A-1024', value: 29800, currency: 'EUR' }, // 298.00 EUR
  ]);
</script>

Вызов до загрузки скрипта: очередь crmq

widget.js подключается с defer, поэтому какое-то мгновение window.crmTrack ещё не существует. События, отправленные слишком рано — при загрузке страницы, в эффекте, из inline-скрипта в <head>, — потерялись бы. Для этого есть очередь:

Работает и до, и после загрузки widget.jsjs
window.crmq = window.crmq || [];
crmq.push(['purchase', { order_id: 'A-1024', items: 2 }, { id: 'A-1024', value: 29800, currency: 'EUR' }]);

Каждый элемент — массив из тех же трёх аргументов, что у crmTrack: имя, параметры, опции. Загрузившись, widget.js проигрывает всё, что накопилось в очереди. Дальше crmq.push отправляет сразу — так что можно всегда пользоваться очередью и вообще не думать о порядке загрузки.

Маленький помощник, которого можно звать когда угодноts
type CrmParams = Record<string, string | number | boolean>;
type CrmOptions = { id?: string; value?: number; currency?: string };

export function track(name: string, params?: CrmParams, options?: CrmOptions) {
  if (typeof window === 'undefined') return; // серверный рендер: делать нечего
  window.crmq = window.crmq || [];
  window.crmq.push([name, params || {}, options || {}]);
}

Параметры и типы

Параметры сверяются с описанием в CRM и приводятся к объявленному типу по таким правилам:

Тип в CRMЧто принимаетПолезно знать
СтрокаЛюбое значениеОбрезается до 500 символов. Объекты превращаются в JSON-строку — но плоские значения в отчётах смотрятся куда приятнее.
ЧислоКонечные числа, по модулю до 1012Округляется до 4 знаков после запятой.
Да/нетtrue, false, "true", "false", 1, 0Удобно, когда значение берётся из атрибута data-*.

Ключи параметров: латиница, цифры и подчёркивания, начинаются с буквы, до 40 символов. Не больше 20 параметров на событие.

Деньги и идемпотентность

value и currency

Сумма передаётся целым числом в минимальных единицах: центах, копейках, сатоши — в чём угодно, что у вашей валюты самое мелкое. 29800 при EUR — это 298.00 €. Дробные деньги в базе рано или поздно дают копеечные расхождения, поэтому мы их просто не допускаем.

  • value — целое число от 0 до 1012.
  • currency — 1–10 латинских букв или цифр (EUR, USD, UAH, USDT). Не передали — возьмётся валюта события.
  • Оба поля сохраняются, только если у события отмечено Событие приносит деньги; иначе value будет null.
const total = 298.0;                         // что показывает корзина
const value = Math.round(total * 100);       // 29800 — что ждёт Diil

id: посчитать один раз

Страницу «Спасибо за заказ» перезагружают, открывают из истории, пересылают себе на телефон. Передайте id (в браузере) или event_id (с сервера) — и Diil сохранит событие один раз, сколько бы раз оно ни пришло. До 128 символов, уникален в пределах сайта. Номер заказа — идеальный кандидат.

События с сервера: POST /marketing/event

Деньги в браузере не считают. Блокировщик рекламы может срезать запрос, покупатель закроет вкладку раньше, чем загрузится «Спасибо», а вызвать crmTrack('purchase') из консоли может кто угодно. Зато ваш сервер точно знает, когда оплата подтвердилась. Оттуда и отправляйте:

POST https://back.sitecog.com/marketing/event
content-type: application/json
x-event-key: sk_…

Секретные ключи

Серверные события подписываются секретным ключом: Маркетинг → События → Секретные ключи → Выпустить ключ. Выглядит он как sk_ + 48 шестнадцатеричных символов. На сайт — до 5 активных ключей (удобно по одному на сервер или интеграцию), любой можно отозвать в CRM. Заявки с сервера отправляются с теми же ключами.

Запрос

x-event-keyheaderstringобязательно
Ваш секретный ключ, sk_….
namestringобязательно
Имя события, как в CRM. Короткий синоним: n.
event_idstringнеобязательно
Ключ идемпотентности, до 128 символов, уникален в пределах сайта. На повтор придёт duplicate: true, и второй раз событие не сохранится. Синоним: id.
visitor_idstringнеобязательно
crm_vid посетителя из браузера. Синоним: vid.
session_idstringнеобязательно
crm_sid посетителя. С ним событие унаследует канал, UTM-метки и рекламную ссылку этой сессии. Синоним: sid.
paramsobjectнеобязательно
Параметры события — по тем же правилам, что и в браузере. Синоним: p.
valueintegerнеобязательно
Сумма в минимальных единицах, от 0 до 1012. Синоним: val.
currencystringнеобязательно
Обозначение валюты; по умолчанию — валюта события. Синоним: cur.
urlstringнеобязательно
Страница, к которой относится событие, например оформление заказа. Синоним: u.

Всё тело запроса должно уложиться в 8 КБ.

// Node 18+ — fetch встроен
export async function sendPurchase(order) {
  const res = await fetch('https://back.sitecog.com/marketing/event', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-event-key': process.env.DIIL_EVENT_KEY,
    },
    body: JSON.stringify({
      name: 'purchase',
      event_id: order.id,              // "A-1024" — повторять безопасно
      visitor_id: order.crmVid,        // сохранили при оформлении, может быть null
      session_id: order.crmSid,
      params: { order_id: order.id, items: order.items.length },
      value: order.totalCents,         // 29800 = 298.00
      currency: 'EUR',
      url: 'https://shop.example/checkout',
    }),
    signal: AbortSignal.timeout(5000),
  });

  if (!res.ok) {
    console.error('Diil не принял событие:', res.status, await res.text());
  }
  return res.ok;
}

Ответ

200 OKjson
{ "ok": true, "duplicate": false }
200 OK — тот же event_id ещё разjson
{ "ok": true, "duplicate": true }
okboolean
Событие принято.
duplicateboolean
True, если событие с таким event_id уже есть. Ничего нового не сохранено — и это нормально, а не ошибка.

Ошибки

В отличие от браузера, серверный адрес прямо говорит, что пошло не так:

СтатусТелоЧто случилось
400{"message":"invalid_body"}Тело — не JSON или не тот объект, который ожидается.
400{"message":"invalid_event_name"}Имя нарушает формат: латиница, цифры, подчёркивания, начинается с буквы, до 64 символов.
400{"message":"unknown_event","name":"purchse"}Такого события нет в Маркетинг → События. Опечатка или ещё не описали. В ответе — имя, которое вы прислали.
401{"message":"invalid_key"}Нет x-event-key, он кривой или отозван.
413—Тело больше 8 КБ.
429{"message":"rate_limit_exceeded"}Слишком много запросов за минуту. См. Лимиты.

Само по себе серверное событие ничего не знает о рекламе: ваш сервер понятия не имеет, что покупатель три дня назад пришёл из поста в Instagram. А браузер знает. Значит, нужно донести идентификаторы виджета из браузера до бэкенда вместе с заказом:

  1. при оформлении заказа прочитайте crm_vid и crm_sid из localStorage;
  2. отправьте их на бэкенд вместе с заказом и сохраните рядом с ним;
  3. когда оплата подтвердится, передайте их как visitor_id и session_id.

Тогда событие унаследует канал, UTM-метки и рекламную ссылку первого просмотра сессии — и в Маркетинг → События будет видно, какой канал и какая ссылка принесли деньги, а не только клики.

// checkout.js — покупатель нажал «Оплатить»
function diilIds() {
  try {
    return {
      crm_vid: localStorage.getItem('crm_vid'),
      crm_sid: localStorage.getItem('crm_sid'),
    };
  } catch {
    return { crm_vid: null, crm_sid: null }; // хранилище закрыто: заказ всё равно уходит
  }
}

const res = await fetch('/api/orders', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ cart, ...diilIds() }),
});

Лимиты

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

ЧтоЛимит
Запросов с одного IP120 в минуту
Запросов на сайт6000 в минуту
Тело запроса8 КБ (больше → 413)
Активных типов событий на сайт100
Параметров у события20
Имя события / ключ параметра64 / 40 символов
Строковое значение параметра500 символов
id / event_id128 символов
Активных секретных ключей на сайт5

Если что-то не так

Событие не появляется

  • Оно не описано. Браузерный адрес всегда отвечает 204 и молча выбрасывает незнакомые события — никаких подсказок, так задумано. Серверный честнее: 400 unknown_event с присланным именем. Сомневаетесь — отправьте то же событие разок через curl и прочитайте ответ.
  • Имя отличается. addToCart в коде и add_to_cart в CRM — два разных события. Копируйте вызов из карточки события.
  • Вы проверяете в режиме Live. Внутри фрейма CRM ничего не считается. Откройте обычную вкладку.
  • Включён режим согласия, а согласия ещё нет. Пока window.crmConsent.status() возвращает 'pending', просмотры и события выбрасываются — и после согласия задним числом не отправятся. Нажмите «Принять» в своём баннере и повторите. Подробнее — в разделе Режим согласия.
  • Скрипт не загрузился или crmTrack вызвали слишком рано. Используйте очередь crmq.
  • Домена нет среди сайтов в CRM. Сайт определяется по Origin страницы (www. не учитывается); незнакомый получает 400 unknown_domain — ищите его во вкладке Network в DevTools.
  • Мешает CSP. Разрешите https://widget.sitecog.com в script-src и https://back.sitecog.com в connect-src.

Событие есть, а параметров нет

Откройте событие в Маркетинг → События. Если там есть строка Приходит, но не описано, параметр до нас дошёл, но в описании события его нет — добавьте его (или исправьте опечатку в коде). И проверьте, что значения подходят под типы из раздела Параметры и типы.

У покупки нет суммы

Отметьте у события Событие приносит деньги и передавайте value целым числом в минимальных единицах — 29800, а не 298.00 и уж точно не "298 €".

Цифры не сходятся с платёжной системой

У части посетителей стоят блокировщики рекламы и расширения для приватности, которые режут запросы аналитики, а кто-то закрывает вкладку до страницы «Спасибо». Браузерные события всегда будут слегка недосчитываться. Для поведения на сайте это нормально, для денег — нет: отправляйте покупки с сервера. Такой запрос не заблокировать, не подделать из консоли, а с event_id он никогда не посчитается дважды.

Как называть события: привычки, которые окупаются

  • snake_case и смысл действия: add_to_cart, signup_completed, purchase, calculator_opened. Не click1 и не ButtonPressed.
  • Называйте результат, а не кнопку. signup_completed переживёт редизайн, green_button_click — нет.
  • Одно событие, много параметров. add_to_cart с { sku, price } лучше, чем add_to_cart_air3, add_to_cart_air4… — и до лимита в 100 типов вы так никогда не дойдёте.
  • Всегда передавайте id для того, что может сработать дважды: покупки, подтверждения, разовые регистрации.
  • Поведение — из браузера, деньги — с сервера. Клики и шаги через crmTrack; оплаты, возвраты и подписки — с вашего бэкенда.
  • Заполняйте описание в CRM. «Срабатывает, когда платёжный сервис подтвердил списание» сэкономит вам совещание через полгода.

Что дальше