Бесплатный экспресс-аудит сайта Безопасность, SEO, AI-поиск и 152-ФЗ — отчёт с баллами за минуту Проверить сайт
Обсудить проект

Сквозная аналитика Битрикс24: почему у лидов нет источника и как это починить

Оценить статью

Типичная картина. На сайте стоит виджет Битрикс24, в карточке лида есть блок «Сквозная аналитика», отчёты в CRM открываются. А источника у половины заявок нет — или написано «Прочий трафик», что по смыслу то же самое.

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

Как это устроено

В сквозной аналитике Битрикс24 три сущности, и путаница между ними и создаёт большинство проблем.

  • Визит — посещение сайта. Его записывает виджет, который вы ставите на страницы.
  • Трек (trace) — строка с данными визита: адрес входа, метки, идентификаторы рекламных систем. Это пропуск, по которому портал связывает визит с заявкой.
  • Лид или сделка — то, что создаётся в CRM. У него есть собственные поля UTM_SOURCE, UTM_MEDIUM и остальные.

Отчёты строятся по связке визита и лида. Нет трека — связи нет, и в отчёте заявка висит без источника, даже если поля UTM_* у неё заполнены.

Что виджет кладёт в трек

Здесь главная неожиданность. Откройте на своём сайте консоль браузера и выполните:

b24Tracker.guest.getTrace()

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

{
  "url": "https://example.ru/uslugi/",
  "device": { "isMobile": false },
  "tags": { "ts": 1794077250, "list": {}, "gclid": null },
  "client": { "yaId": 82521196 },
  "pages": { "list": [["https://example.ru/uslugi/", 1794077251, "Услуги"]] },
  "gid": null,
  "previous": { "list": [] }
}

Обратите внимание на tags.list — он пустой. Виджет записывает туда только то, что было в адресе страницы: utm_source, utm_medium, yclid, gclid и прочие метки.

А теперь посчитайте, сколько ваших посетителей приходит с метками. Реклама — да. Всё остальное — нет:

  • органика из Яндекса и Google;
  • переходы из нейросетей и ассистентов с поиском;
  • переходы по ссылкам из соцсетей, мессенджеров и чужих статей;
  • прямые заходы и переходы из закладок.

У всех этих визитов трек уходит с пустым tags.list. Реферер виджет в трек не кладёт — хотя браузер его знает. Так и появляется «Прочий трафик» в отчётах.

Три способа принимать заявки — и что с источником в каждом

CRM-формы Битрикс24

Самый простой случай: форма сделана в портале и встроена на сайт кодом вида b24form. Источник подтягивается сам, потому что форма и трекер — части одной системы.

Ограничение тоже понятное: форма выглядит как форма Битрикс24, а не как ваш сайт, и вёрстка настраивается в узких рамках. Если это устраивает — остальная статья вам не нужна.

Свои формы с отправкой на сервер

Как только форма своя, а заявка уходит через crm.lead.add, связь с визитом теряется: портал не знает, что обращение относится к тому самому визиту. Трек нужно взять на странице и передать вместе с заявкой.

На клиенте:

var trace = '';
try {
  if (window.b24Tracker && window.b24Tracker.guest && window.b24Tracker.guest.getTrace) {
    trace = window.b24Tracker.guest.getTrace();
  }
} catch (e) {}

fetch('/api/lead/', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: name, phone: phone, trace: trace })
});

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

На сервере трек идёт рядом с полями, а не внутри них. Это частая ошибка: поля TRACE среди полей лида нет, портал ждёт отдельный параметр:

$payload = [
    'fields' => [
        'TITLE' => 'Заявка с сайта',
        'NAME'  => $name,
        'PHONE' => [['VALUE' => $phone, 'VALUE_TYPE' => 'WORK']],
    ],
    'params' => ['REGISTER_SONET_EVENT' => 'N'],
];

if ($trace !== '') {
    $payload['trace'] = $trace;
}

$http = new \Bitrix\Main\Web\HttpClient(['socketTimeout' => 10, 'waitResponse' => true]);
$response = $http->post($webhook, $payload);

Заявка уже создана, а трек появился позже

Бывает, что лид создаётся на стороне — из колл-центра, из чужой CMS, из обработки письма. Привязать визит можно отдельным вызовом:

crm.tracking.trace.add
{
  "TRACE": "{\"tags\":{\"list\":{\"utm_source\":\"perplexity.ai\"}}}",
  "ENTITIES": [{ "TYPE": "LEAD", "ID": 19610 }]
}

Метод принимает трек строкой и связывает его с объектом CRM. Важное следствие: трек можно собрать самостоятельно — достаточно положить метки в tags.list. Этим мы и воспользуемся дальше.

Как размечать переходы без меток

Теперь к главному. Раз виджет не кладёт реферер в трек, положим его сами — и в поля лида, и в сам трек.

Шаг 1. Запомнить первое касание

Клиент редко оставляет заявку на первом визите: пришёл из поиска, ушёл, вернулся через неделю напрямую. Если брать источник в момент отправки формы, все такие заявки станут прямыми заходами. Поэтому первое касание запоминаем:

var KEY = 'src_first';
var FIELDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];

function current() {
  var params = new URLSearchParams(location.search);
  var data = { at: Date.now(), page: location.pathname };
  var hasUtm = false;

  FIELDS.concat(['yclid', 'gclid', 'ysclid']).forEach(function (f) {
    var v = params.get(f);
    if (v) { data[f] = v.slice(0, 200); hasUtm = true; }
  });

  var ref = document.referrer || '';
  if (ref && ref.indexOf(location.origin) !== 0) {
    data.referrer = ref.slice(0, 300);
  }

  return (hasUtm || data.referrer) ? data : null;
}

var now = current();
var first = null;
try { first = JSON.parse(localStorage.getItem(KEY) || 'null'); } catch (e) {}
if (now && !first) {
  try { localStorage.setItem(KEY, JSON.stringify(now)); } catch (e) {}
  first = now;
}

Проверка ref.indexOf(location.origin) !== 0 отсекает переходы внутри сайта: без неё реферером окажется ваша же предыдущая страница.

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

Шаг 2. Вывести источник из реферера

На сервере: если меток нет, берём хост реферера. Нейросети имеет смысл выделить в отдельный канал — это не обычный переход по ссылке и не органика поисковика, а ответ ассистента, где вас порекомендовали. Смешаете с остальным — не увидите, работает ли подготовка сайта к AI-поиску.

function sourceFromReferrer(string $referrer): array
{
    $host = mb_strtolower((string)parse_url(trim($referrer), PHP_URL_HOST));
    $host = preg_replace('~^www\\.~', '', $host);

    if ($host === '') {
        return ['direct', 'none'];
    }

    /* проверяем до поисковиков: gemini живёт на поддомене google.com */
    $ai = ['perplexity.ai', 'chatgpt.com', 'chat.openai.com', 'openai.com',
        'claude.ai', 'gemini.google.com', 'copilot.microsoft.com',
        'you.com', 'phind.com'];
    foreach ($ai as $known) {
        if ($host === $known || str_ends_with($host, '.'.$known)) {
            return [$host, 'ai'];
        }
    }

    $search = ['yandex.ru', 'ya.ru', 'google.com', 'google.ru',
        'bing.com', 'duckduckgo.com', 'mail.ru', 'rambler.ru'];
    foreach ($search as $known) {
        if ($host === $known || str_ends_with($host, '.'.$known)) {
            return [$host, 'organic'];
        }
    }

    $social = ['vk.com', 't.me', 'ok.ru', 'dzen.ru', 'vc.ru', 'habr.com'];
    foreach ($social as $known) {
        if ($host === $known || str_ends_with($host, '.'.$known)) {
            return [$host, 'social'];
        }
    }

    return [$host, 'referral'];
}

Порядок проверок важен: gemini.google.com — поддомен google.com, и если сначала проверять поисковики, переходы из Gemini уедут в органику Google.

Шаг 3. Заполнить поля лида

if (empty($fields['UTM_SOURCE'])) {
    [$source, $medium] = sourceFromReferrer($referrer);
    $fields['UTM_SOURCE'] = $source;
    if (empty($fields['UTM_MEDIUM'])) {
        $fields['UTM_MEDIUM'] = $medium;
    }
}

Шаг 4. Дописать метки в трек

Полей лида мало: отчёты сквозной аналитики строятся по треку. Дописываем метки туда же, своё не затирая:

function traceWithTags(string $trace, array $tags): string
{
    $tags = array_filter($tags, static fn($v): bool => trim((string)$v) !== '');
    if ($trace === '' || !$tags) {
        return $trace;
    }

    $data = json_decode($trace, true);
    if (!is_array($data)) {
        return $trace;
    }

    $list = (array)($data['tags']['list'] ?? []);
    foreach ($tags as $key => $value) {
        if (trim((string)($list[$key] ?? '')) === '') {
            $list[$key] = mb_substr((string)$value, 0, 200);
        }
    }
    $data['tags']['list'] = $list;

    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    return is_string($json) ? $json : $trace;
}

Условие trim(...) === '' нужно, чтобы не перебить настоящие метки рекламы: если человек пришёл по объявлению, в треке уже есть utm_source из адреса, и он точнее любого реферера.

Виджет грузится отложенно — и это ломает трек

Виджет Битрикс24 весит прилично, поэтому его часто подключают отложенно: через пару секунд после загрузки страницы или по первому действию посетителя. Приём правильный, скорость он экономит заметно.

var loaded = false;
function loadWidget() {
  if (loaded) { return; }
  loaded = true;
  var s = document.createElement('script');
  s.async = true;
  s.src = 'https://portal.bitrix24.ru/upload/crm/site_button/loader_X_xxxx.js?' + (Date.now() / 60000 | 0);
  document.body.appendChild(s);
}

['scroll', 'pointerdown', 'keydown', 'touchstart'].forEach(function (e) {
  window.addEventListener(e, loadWidget, { once: true, passive: true });
});
window.addEventListener('load', function () { window.setTimeout(loadWidget, 1500); });

Побочный эффект: b24Tracker появляется только после загрузки виджета. Посетитель, который открыл страницу и сразу отправил форму, уйдёт без трека.

Что с этим делать. Если форма простая и заполняется за десять секунд — грузить виджет сразу на страницах с формой, а отложенную загрузку оставить на остальных. Если формы длинные, квиз или калькулятор — отложенная загрузка не мешает, трекер успевает подняться. И в любом случае поля UTM_* у лида заполняются независимо от трека: даже без связи с визитом источник в карточке будет виден.

Как проверить, что всё работает

  1. Откройте сайт в режиме инкогнито, перейдя по ссылке с меткой: ?utm_source=test&utm_medium=check.
  2. Проскролльте страницу — виджет должен загрузиться.
  3. В консоли выполните b24Tracker.guest.getTrace() и убедитесь, что в tags.list появились ваши метки.
  4. Отправьте тестовую заявку.
  5. Откройте лид в портале: в полях должны быть UTM_SOURCE и UTM_MEDIUM, а в блоке «Сквозная аналитика» — строка с источником, а не «Прочий трафик».
  6. Повторите без меток, перейдя на сайт из поиска. Источник должен определиться по рефереру.

Шестой пункт и есть проверка на то, ради чего всё делалось: с метками работает у всех, без меток — почти ни у кого.

Частые ошибки

Трек передают внутри полей лида. Поля TRACE у лида нет, портал ждёт отдельный параметр рядом с fields. Заявка создастся, ошибки не будет, связи с визитом — тоже.

Берут последнее касание вместо первого. Тогда клиент, который пришёл из рекламы, подумал неделю и вернулся напрямую, будет записан как прямой заход, а реклама останется без заявки. Храните первое касание и передавайте его.

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

Проверяют только рекламу. Реклама приходит с метками и выглядит нормально. Дыра обнаруживается на органике и на переходах из соцсетей, то есть там, куда смотрят реже.

Зачем это нужно

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

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

Если ковыряться в этом некогда, настройку сквозной аналитики мы делаем под ключ: подключаем трекер и формы, разбираем источники, сверяем цифры с Метрикой и показываем, где отчёт врёт и почему.

Посмотрите, что видно снаружи

Экспресс-аудит проверит разметку, счётчики и техническую часть сайта — за минуту, бесплатно и без доступов.

Проверить сайт

Статья была полезна?

Оцените первым — так другие читатели поймут, стоит ли читать

Частые вопросы

Почему в карточке лида написано «Прочий трафик»?

Значит трек до лида дошёл, но меток в нём не было. Так происходит со всеми переходами без UTM: из поиска, из соцсетей, из нейросетей, по прямому заходу. Виджет кладёт в трек только то, что было в адресе страницы, а реферер — не кладёт. Лечится тем, что источник выводят из реферера и дописывают в трек самостоятельно.

Нужен ли виджет, если формы свои?

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

Можно ли собрать трек вручную, без виджета?

Да. Метод crm.tracking.trace.add принимает строку трека и привязывает её к лиду или сделке; метки передаются в объекте tags.list. Это рабочий путь для заявок, которые создаются вне сайта, — из колл-центра или из чужой системы.

Что делать с переходами из нейросетей?

Выделять в отдельный канал. Ассистенты приходят без меток и по рефереру определяются как обычные переходы по ссылке, из-за чего теряются среди прочего трафика. Отдельный канал показывает, окупается ли подготовка сайта к AI-поиску. Часть ассистентов реферер не передаёт вовсе — такие визиты останутся прямыми заходами, и с этим ничего не сделать.

Почему метки есть в Метрике, но нет в Битрикс24?

Счётчик Метрики срабатывает при загрузке страницы, а трек Битрикс24 собирается виджетом, который часто подключают отложенно. Если заявку отправили раньше, чем виджет загрузился, трека не будет. На страницах с формами виджет стоит грузить сразу.