Тег widget.js, який ви вже додали для редагування на сайті, тихенько рахує кожен перегляд сторінки, з’ясовує, звідки прийшов відвідувач і яка реклама його привела. Додайте один рядок JavaScript — або один запит із сервера — і ви побачите ще й тих, хто додав товар у кошик, зареєструвався і справді заплатив, разом із сумою. Без другого скрипта аналітики, без менеджера тегів і без переписування cookie-банера.
Ось що в меню:
- Перегляди сторінок — автоматично, нуль коду. Канали, UTM-мітки, ідентифікатори рекламних кліків, геолокація, пристрої.
- Власні події з браузера —
window.crmTrack('add_to_cart', …)для UX-сигналів. - Серверні події —
POST /marketing/eventіз секретним ключем, для покупок і всього, що треба порахувати рівно один раз.
Усе це потрапляє в CRM у розділ Маркетинг: Відвідуваність — для візитів, Події — для ваших власних подій, Рекламні канали — для посилань із мітками.
Що працює одразу: перегляди сторінок
Якщо на сторінці є widget.js, перегляди вже рахуються. Тег той самий, що й у редагуванні на сайті:
<script src="https://widget.sitecog.com/widget.js" defer></script>Під час кожного завантаження сторінки віджет надсилає один перегляд через navigator.sendBeacon — крихітний запит за принципом «відправив і забув», який не гальмує сторінку й переживає навіть закриття вкладки. Сервер відповідає 204 No Content; читати у відповіді нічого.
Що збирається
З браузера:
- URL сторінки, заголовок і referrer;
- UTM-мітки:
utm_source,utm_medium,utm_campaign,utm_term,utm_content; - ідентифікатори рекламних кліків:
gclid,gbraid,wbraid(Google),yclid(Yandex),fbclid(Meta),msclkid(Microsoft); - код рекламного посилання
?dl=з посилань рекламних каналів; - кілька рекламних cookie, якщо на вашому сайті вони вже є:
_ga,_gcl_*,_ym_uid,_fbp,_fbc. У режимі згоди віджет не читає їх, доки відвідувач не погодиться, а якщо браузер надсилає Global Privacy Control — не читає взагалі; - розмір екрана й вікна перегляду, щільність пікселів, мова браузера й часовий пояс.
Додається на нашому боці:
- місцезнаходження — за IP-адресою;
- тип пристрою, браузер і операційна система;
- канал трафіку: Реклама, Соцмережі, Пошук, Переходи, Листи або Прямі заходи;
- новий це відвідувач чи той, що повернувся.
Зберігаємо ми менше, ніж отримуємо: IP-адресу — з обнуленим хвостом, а з URL прибираємо якір і чутливі параметри на кшталт token чи email. Подробиці — в розділі «Що ми зберігаємо і як довго».
Ботів не викидають — їх позначають і відфільтровують зі звітів. Тож цифри, на які ви дивитеся, — про людей, а сирі дані нікуди не діваються, якщо вам колись стане цікаво, скільки вашого трафіку — це краулери (спойлер: більше, ніж хотілося б).
Звіти живуть у Маркетинг → Відвідуваність: відвідувачі, сесії, канали, сторінки, географія й пристрої.
Доба й години у звітах
«Учора» у звіті — це вчора в часовому поясі вашого сайту, а не за UTC і не в поясі того, хто зараз дивиться на графік. Магазин у Києві бачить вечірній пік о 20:00, а не о 17:00, а власник у Києві й менеджер у Нью-Йорку дивляться на ту саму добу.
- Пояс задається в CRM → Налаштування → Часовий пояс. Поки його ніхто не вибрав, CRM бере часовий пояс браузера власника.
- За ним ріжеться доба у Відвідуваності, Рекламних каналах і Подіях і рахуються години на графіках.
- Зміна пояса перераховує й минулі звіти. Самі дані не змінюються — змінюється лише те, де проходить північ.
Ідентифікатори відвідувача й сесії
Віджет упізнає відвідувача, що повернувся, без cookie. Він тримає три ключі в localStorage:
| Ключ | Що це | Скільки живе |
|---|---|---|
crm_vid | Ідентифікатор відвідувача | Доки відвідувач не очистить дані сайту |
crm_sid | Ідентифікатор сесії | Нова сесія починається після 30 хвилин бездіяльності |
crm_sat | Час останньої активності — за ним вирішується, коли сесія завершилась | Оновлюється, поки відвідувач гортає сайт |
Якщо сховище заблоковано (так роблять деякі приватні режими), ідентифікатори живуть у пам’яті, доки відкрита вкладка. Запам’ятайте crm_vid і crm_sid — вони знадобляться, щоб прив’язати серверні події до відвідувача. У режимі згоди ці ключі з’являються лише після згоди відвідувача, а crmConsent.deny() їх стирає.
Односторінкові застосунки
В односторінковому застосунку (React Router, клієнтська навігація Next.js, Vue Router) сторінка не перезавантажується, але віджет однаково помічає кожен перехід — автоматично, без жодного коду. Він перехоплює history.pushState і history.replaceState та слухає подію popstate (кнопки «Назад» і «Вперед»). Коли змінився шлях або рядок запиту, віджет чекає 300 мс — щоб ланцюжок швидких перенаправлень дав один перегляд, а не п’ять, — і надсилає перегляд нової сторінки.
Referrer такого «віртуального» перегляду — попередня сторінка вашого ж сайту. Тож шлях відвідувача всередині застосунку у звітах виглядає так само, як на звичайному багатосторінковому сайті.
Поведінку можна підлаштувати атрибутом data-spa на тегу:
| Значення | Що рахується |
|---|---|
| не вказано | За замовчуванням: перегляд на кожну зміну шляху або рядка запиту. |
hash | Те саме плюс зміни якоря (#/route) — для роутерів, що живуть у hash. |
off | Відстеження SPA вимкнено: один перегляд на одне завантаження скрипта, як було раніше. |
<script src="https://widget.sitecog.com/widget.js" data-spa="hash" defer></script>Приватність і згода
Віджет не ставить cookie. А якщо вашому сайту потрібна згода відвідувача перед аналітикою, увімкніть режим згоди: віджет мовчатиме, доки ваш банер не скаже «так». Скрипт при цьому стоїть на сторінці з самого початку — притримувати його не треба.
Режим згоди
Додайте до тегу атрибут data-consent="required":
<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-міток і рекламного посилання.
Без атрибута нічого не змінюється: рахунок починається одразу після завантаження скрипта, як і раніше.
crmConsent: передаємо рішення відвідувача
Вашому банеру потрібен лише window.crmConsent:
grant()functiondeny()functioncrm_vid, crm_sid і crm_sat. Працює навіть без атрибута data-consent — див. нижче.status()'granted' | 'denied' | 'pending''pending' — відвідувач ще нічого не обрав.requiredbooleantrue, якщо на тегу стоїть data-consent="required".gpcbooleantrue, якщо браузер надсилає Global Privacy Control.Вибір зберігається в localStorage під ключем crm_consent, тож на наступних візитах відвідувача вже не треба питати вдруге.
Банер часто малюється раніше, ніж підвантажиться відкладений widget.js. На цей випадок є черга — та сама ідея, що й у crmq:
window.crmConsent = window.crmConsent || [];
crmConsent.push('grant'); // або 'deny'push працює й після завантаження, тож банеру не треба знати, чи скрипт уже на місці, — просто завжди користуйтеся push.
Коли вибір змінюється, на window спрацьовує подія crm:consent. Що саме обрано, дізнавайтеся в обробнику через window.crmConsent.status():
window.addEventListener('crm:consent', () => {
console.log('Згода:', window.crmConsent.status()); // 'granted' або 'denied'
});Ось банер цілком — з кнопками «Прийняти» й «Відхилити», який не набридає тим, хто вже обрав:
<div id="consent-banner" hidden>
Ми рахуємо відвідування, щоб робити сайт кращим. Не заперечуєте?
<button id="consent-accept">Прийняти</button>
<button id="consent-reject">Відхилити</button>
</div>
<script>
window.crmConsent = window.crmConsent || [];
const banner = document.getElementById('consent-banner');
function choose(choice) {
crmConsent.push(choice); // 'grant' або 'deny'
banner.hidden = true;
}
document.getElementById('consent-accept').addEventListener('click', () => choose('grant'));
document.getElementById('consent-reject').addEventListener('click', () => choose('deny'));
// На load відкладений widget.js уже відпрацював, і status() знає про минулий вибір
window.addEventListener('load', () => {
if (typeof window.crmConsent.status !== 'function') return; // віджет не завантажився
banner.hidden = window.crmConsent.status() !== 'pending';
});
</script>// ConsentBanner.tsx
'use client';
import { useEffect, useState } from 'react';
type ConsentStatus = 'granted' | 'denied' | 'pending';
declare global {
interface Window {
// До завантаження widget.js це просто масив-черга, після — API зі status()
crmConsent?: { push(choice: 'grant' | 'deny'): unknown; status?: () => ConsentStatus };
}
}
function readStatus(): ConsentStatus | null {
return typeof window.crmConsent?.status === 'function' ? window.crmConsent.status() : null;
}
function choose(choice: 'grant' | 'deny') {
window.crmConsent = window.crmConsent || [];
window.crmConsent.push(choice);
}
export function ConsentBanner() {
const [open, setOpen] = useState(false);
useEffect(() => {
const sync = () => setOpen(readStatus() === 'pending');
sync(); // віджет уже завантажився
window.addEventListener('load', sync); // або ось-ось завантажиться
window.addEventListener('crm:consent', sync); // вибір змінився
return () => {
window.removeEventListener('load', sync);
window.removeEventListener('crm:consent', sync);
};
}, []);
if (!open) return null;
return (
<div className="consent-banner">
Ми рахуємо відвідування, щоб робити сайт кращим. Не заперечуєте?
<button onClick={() => choose('grant')}>Прийняти</button>
<button onClick={() => choose('deny')}>Відхилити</button>
</div>
);
}Global Privacy Control і відмова без банера
Деякі браузери й розширення надсилають сигнал Global Privacy Control (navigator.globalPrivacyControl) — «не передавайте мої дані». Якщо він увімкнений, віджет ніколи не читає рекламні cookie, у будь-якому режимі — з атрибутом data-consent чи без нього. Чи надсилає браузер цей сигнал, підкаже window.crmConsent.gpc.
А deny() діє навіть без атрибута data-consent. Тобто відмовитися від відстеження можна на будь-якому сайті з віджетом — навіть там, де банера згоди немає й рахунок іде з першого перегляду. Наприклад, посиланням у підвалі:
<footer>
<a href="#" id="no-tracking">Не відстежувати мене</a>
</footer>
<script>
document.getElementById('no-tracking').addEventListener('click', (e) => {
e.preventDefault();
window.crmConsent = window.crmConsent || [];
crmConsent.push('deny'); // запам’ятовує відмову й стирає crm_vid / crm_sid / crm_sat
e.currentTarget.textContent = 'Готово, вас не відстежуємо';
});
</script>Що ми зберігаємо і як довго
IP-адреси — обрізаними. В IPv4 обнуляється останній октет (203.0.113.57 → 203.0.113.0), IPv6 урізається до /48. Країну й місто ми визначаємо за повною адресою ще до обрізання, тож звіти з географії працюють як і раніше.
URL — без зайвого. З адрес сторінок, referrer, сторінки входу й URL подій прибираються якір (#…) і чутливі параметри:
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 днів), а потім видаляються. Строк налаштовує оператор інсталяції. На заявки це не поширюється — вони не видаляються.
Відвідувач може попросити забути його раніше. Власник сайту видаляє його профіль, перегляди й події за ідентифікатором відвідувача просто з CRM; заявки — лише якщо це вибрано окремо. Докладніше — у розділі Заявки → Видалення даних людини.
Рекламні посилання: яка реклама привела відвідувача
У Маркетинг → Рекламні канали ви створюєте посилання з мітками для кожного місця, де рекламуєтеся, — допис у соцмережі, розсилка, банер на чужому сайті. Кожне посилання веде на ваш сайт і несе UTM-мітки плюс короткий код:
https://shop.example/?utm_source=instagram&utm_medium=social&utm_campaign=autumn_sale&dl=k3Zp9QaW1xdl — це 10-символьний код, що ідентифікує посилання. На сайті нічого робити не треба: віджет підхоплює його з першим переглядом, і кожен візит, подія та заявка в цій сесії приписуються посиланню. По кожному посиланню в CRM є окрема статистика.
Власні події
Перегляди показують, куди люди ходили. Події — що вони робили: додали в кошик, зареєструвалися, відкрили калькулятор цін, заплатили. Ви описуєте подію один раз у CRM, а потім надсилаєте її з браузера або з сервера.
Крок нуль: оголосіть подію в CRM
Diil приймає лише ті події, про які знає. Це фіча: одруківка у вашому коді не створить тихенько новий тип події й не розколе ваші звіти навпіл.
Відкрийте Маркетинг → Події й натисніть «Створити подію»
Угорі має бути обраний сайт (домен).Назвіть її точно так, як у коді
add_to_cart,signup_completed,purchase. Латинські літери, цифри й підкреслення, починаючи з літери, до 64 символів. Додайте зрозумілу людям назву й опис — ви з майбутнього скажете дякую.Опишіть параметри
До 20 на подію, кожен зі своїм типом: Рядок, Число або Так/ні (boolean). Усе, що не описано тут, до бази не потрапить.Гроші? Позначте «Подія приносить гроші»
І задайте назву валюти (EUR,USD,USDTчи навіть власні бали лояльності). Без цієї позначкиvalueподії не зберігається.Скопіюйте готовий виклик
CRM показує точний викликcrmTrackі серверний запит для цієї події. Вставляйте й вперед.
Сайт може мати до 100 активних типів подій. Щойно за подією з’являються дані, її ім’я блокується (воно вже у вашому коді), і подію можна відправити до архіву, але не видалити, — тож у звітах ніколи не з’являться рядки без назви.
Надсилайте події з браузера: crmTrack
window.crmTrack(
name: string,
params?: Record<string, string | number | boolean>,
options?: { id?: string; value?: number; currency?: string },
): voidПрацює за принципом «відправив і забув»: нічого не повертає, ніколи не кидає у вас винятками й надсилає через sendBeacon, тож подія переживе навіть перехід на іншу сторінку після кліку. Ідентифікатори відвідувача й сесії додаються автоматично — ви лише описуєте, що сталося.
Аргументи
namestringобовʼязковоparamsobjectнеобовʼязковоЗа замовчуванням: {}{ sku: 'air3-graphite', price: 149 }. Ключі підкоряються тим самим правилам, що й імена, до 40 символів. Зберігаються лише параметри, описані в CRM; значення перетворюються до оголошеного типу — див. «Параметри й типи».options.idstringнеобовʼязковоЗа замовчуванням: немаєid двічі — і подію буде збережено один раз. Для покупок використовуйте номер замовлення.options.valueintegerнеобовʼязковоЗа замовчуванням: немає149900 означає 1499,00. Від 0 до 1012. Зберігається, лише якщо в події ввімкнено «Подія приносить гроші».options.currencystringнеобовʼязковоЗа замовчуванням: валюта події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>// diil.d.ts — один раз розкажіть TypeScript про віджет
export {};
type CrmParams = Record<string, string | number | boolean>;
type CrmOptions = { id?: string; value?: number; currency?: string };
declare global {
interface Window {
crmTrack?: (name: string, params?: CrmParams, options?: CrmOptions) => void;
crmq?: unknown[];
}
}
// AddToCartButton.tsx
type Product = { sku: string; price: number };
export function AddToCartButton({ product, onAdd }: { product: Product; onAdd: () => void }) {
const handleClick = () => {
onAdd();
window.crmTrack?.('add_to_cart', { sku: product.sku, price: product.price });
};
return <button onClick={handleClick}>Додати в кошик</button>;
}// app/checkout/success/PurchaseTracker.tsx
'use client';
import { useEffect } from 'react';
type Props = { orderId: string; items: number; totalCents: number; currency: string };
export function PurchaseTracker({ orderId, items, totalCents, currency }: Props) {
useEffect(() => {
// Ефект може спрацювати до завантаження widget.js — черга на нього зачекає
window.crmq = window.crmq || [];
window.crmq.push([
'purchase',
{ order_id: orderId, items },
{ id: orderId, value: totalCents, currency },
]);
}, [orderId, items, totalCents, currency]);
return null;
}
// app/checkout/success/page.tsx (Server Component)
// <PurchaseTracker orderId="A-1024" items={2} totalCents={29800} currency="EUR" />Виклики до завантаження скрипта: черга crmq
widget.js завантажується з defer, тож якусь мить window.crmTrack ще не існує. Події, надіслані зарано — під час завантаження сторінки, в ефекті, з інлайн-скрипта в <head>, — загубилися б. Черга це виправляє:
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 надсилає одразу — тож можна просто завжди користуватися чергою й не думати про порядок завантаження.
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 і приводяться до оголошеного типу за такими правилами:
| Оголошений тип | Приймає | Варто знати |
|---|---|---|
| Рядок | Будь-яке значення | Обрізається до 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 — те, що хоче Diilid: порахувати один раз
Сторінки подяки перезавантажують, відкривають з історії, пересилають на другий пристрій. Передайте 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обовʼязковоn.event_idstringнеобовʼязковоduplicate: true, і подія вдруге не зберігається. Синонім: id.visitor_idstringнеобовʼязковоcrm_vid відвідувача з браузера. Синонім: vid.session_idstringнеобовʼязковоcrm_sid відвідувача. З ним подія успадковує канал, UTM-мітки й рекламне посилання цієї сесії. Синонім: sid.paramsobjectнеобовʼязковоp.valueintegerнеобовʼязково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 event failed:', res.status, await res.text());
}
return res.ok;
}<?php
function send_purchase(array $order): bool
{
$payload = [
'name' => 'purchase',
'event_id' => $order['id'], // "A-1024" — повтори безпечні
'visitor_id' => $order['crm_vid'], // збережено під час оформлення, може бути null
'session_id' => $order['crm_sid'],
'params' => ['order_id' => $order['id'], 'items' => count($order['items'])],
'value' => $order['total_cents'], // 29800 = 298,00
'currency' => 'EUR',
'url' => 'https://shop.example/checkout',
];
$ch = curl_init('https://back.sitecog.com/marketing/event');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-event-key: ' . getenv('DIIL_EVENT_KEY'),
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
error_log("Diil event failed: $status $body");
return false;
}
return true;
}curl -X POST https://back.sitecog.com/marketing/event \
-H "content-type: application/json" \
-H "x-event-key: $DIIL_EVENT_KEY" \
-d '{
"name": "purchase",
"event_id": "A-1024",
"visitor_id": "<crm_vid>",
"session_id": "<crm_sid>",
"params": { "order_id": "A-1024", "items": 2 },
"value": 29800,
"currency": "EUR",
"url": "https://shop.example/checkout"
}'Відповідь
{ "ok": true, "duplicate": false }{ "ok": true, "duplicate": true }okbooleanduplicatebooleanevent_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 три дні тому. А браузер знає. Тож фокус у тому, щоб донести ідентифікатори віджета з браузера на ваш бекенд разом із замовленням:
- під час оформлення замовлення прочитайте
crm_vidіcrm_sidзlocalStorage; - надішліть їх на свій бекенд разом із замовленням і збережіть поруч із ним;
- коли оплату підтверджено, передайте їх як
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() }),
});// server.js (Express)
app.post('/api/orders', async (req, res) => {
const { cart, crm_vid, crm_sid } = req.body;
// Зберігайте ідентифікатори із замовленням: оплату часто підтверджує пізніше вебхук
const order = await db.orders.create({ cart, crmVid: crm_vid, crmSid: crm_sid });
res.json({ id: order.id, payUrl: await createPayment(order) });
});
// Ваш платіжний провайдер викликає це, коли гроші справді надійшли
app.post('/webhooks/payment', async (req, res) => {
const order = await db.orders.markPaid(req.body.orderId);
// 3 · Diil — аналітика ніколи не повинна ламати оформлення замовлення
try {
await sendPurchase(order); // функція з розділу «Серверні події» вище
} catch (err) {
console.error('Diil недоступний, повторимо пізніше', err);
}
res.sendStatus(200);
});Ліміти
Ліміти спільні для всіх маркетингових маршрутів — переглядів, подій і заявок разом — і рахуються у фіксованих хвилинних вікнах.
| Що | Ліміт |
|---|---|
| Запити з одного IP | 120 за хвилину |
| Запити на сайт | 6000 за хвилину |
| Тіло запиту | 8 КБ (більше → 413) |
| Активні типи подій на сайт | 100 |
| Параметри на подію | 20 |
| Ім’я події / ключ параметра | 64 / 40 символів |
| Значення рядкового параметра | 500 символів |
id / event_id | 128 символів |
| Активні секретні ключі на сайт | 5 |
Якщо щось не так
Подія не з’являється
- Її не оголошено. Браузерна адреса завжди відповідає
204і мовчки відкидає невідомі імена подій — жодних підказок, так задумано. Серверна адреса чесна:400 unknown_eventз надісланим вами ім’ям. Коли сумніваєтеся, надішліть ту саму подію один раз через curl і прочитайте відповідь. - Ім’я відрізняється.
addToCartу коді йadd_to_cartу CRM — дві різні речі. Копіюйте виклик із картки події. - Ви тестуєте в режимі Live. Усередині iframe 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 €".
Цифри не збігаються з платіжною системою
У частини відвідувачів стоять блокувальники реклами чи розширення приватності, що блокують запити аналітики, а дехто закриває вкладку до сторінки подяки. Браузерних подій завжди буде трохи менше. Для UX-сигналів це нормально, для грошей — ні: надсилайте покупки з сервера — його не заблокувати, не підробити з консолі, а з 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. «Спрацьовує, коли платіжний провайдер підтверджує списання» — і через пів року не знадобиться окрема нарада.