Обсудить задачу

Заявки из чата приходят в сквозную аналитику без источника

Чат на сайте отдаёт заявки в сквозную аналитику через вебхук, и они приходят без источника: не видно ни рекламной кампании, ни страницы, с которой человек написал. Причина обычно не в аналитике и не в настройках чата.

Почему заявки из чата приходят без источника

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

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

PHPТихая поломка: строка читается, значение всегда пустое
// Выглядит правильно, а не работает никогда:
// вебхук приходит сервер-серверу, cookie roistat_visit в нём нет,
// а поля roistat_visit в форме чата никто не заводил.
$roistatVisit = isset($form['roistat_visit']) ? $form['roistat_visit'] : '';

Факты в одном из разборов сошлись с точностью до строчки. В кабинете аналитики 23 заявки из 23 по каналу чата — без номера визита, источник помечен как неопределённый. В логе обработчика за семь месяцев 182 события, из них 26 контактных форм, и во всех payload метка визита пустая.

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

Как передать метку визита, если cookie в вебхук не попадает

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

JavaScriptМетка визита уходит в виджет с сайта, а не из кабинета чата
window.replainSettings = {
  id: 'идентификатор чата',
  // Метку кладём в произвольное поле клиента: клиент его не видит,
  // в вебхуке оно придёт вместе с данными формы.
  fields: {
    roistat_visit: (document.cookie.match(/(?:^|;\s*)roistat_visit=([^;]+)/) || [])[1] || ''
  },
  onWidgetLoaded: function () {
    // Счётчик аналитики ставит cookie асинхронно и может не успеть
    // к моменту загрузки виджета — досылаем поле, когда она появится.
    var tries = 0, t = setInterval(function () {
      var m = document.cookie.match(/(?:^|;\s*)roistat_visit=([^;]+)/);
      if (m) { window.ReplainAPI('setFields', { roistat_visit: m[1] }); clearInterval(t); }
      if (++tries > 30) { clearInterval(t); }
    }, 1000);
  }
};
  • Cookie аналитики ставится асинхронно, поэтому одной строки в настройках мало — нужна досылка после загрузки виджета
  • Имя, почту и телефон через этот же механизм передавать нельзя: если передать значения всех полей формы, форма клиенту показана не будет
  • Поле сохраняется в карточке клиента на стороне чата, поэтому приходит и в последующих событиях того же диалога, а не только в момент отправки формы

Проверять это надо на живом трафике, а не на тестовом запросе. В логе после правки видно и метку в payload, и ответ аналитики: пришёл визит 467383, заявка создана.

Все ли события чата приносят контакты

Нет. Контакты клиента несут четыре разных события, а обработчик в том разборе реагировал на одно — остальные три логировались и пропускались, то есть эти обращения не попадали в аналитику вообще.

СобытиеКогда возникаетОбрабатывалось
Контактная формаКлиент заполнил форму в виджетеда
Офлайн-формаОператоры не в сети, клиент оставил контактынет
Авто-запрос контактовЧат сам просит контакты по ходу диалоганет
Запрос обратного звонкаКлиент просит перезвонитьнет

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

Что вебхук требует от обработчика

Ответ 200 и быстро. У этого сервиса таймаут обработчика 10 секунд и до 5 повторов примерно раз в минуту; если ответ не 200, доставка всех последующих событий этого клиента приостанавливается, а по исчерпании попыток события удаляются.

  • Отвечать 200 всегда, даже когда событие вам неинтересно — иначе сервис перестанет присылать события по этому клиенту
  • Не поднимать CMS ради вебхука: из ядра берутся только реквизиты подключения к базе, чтобы уложиться в таймаут
  • Быть идемпотентным: до пяти повторов означает, что одно обращение может приехать пять раз
  • Держать секретный токен в адресе обработчика и отдавать 403 без него
PHPЗащита от повторной доставки
// Идемпотентность на уровне схемы, а не логики:
// UNIQUE по идентификатору события + INSERT IGNORE.
// Повтор доставки не создаст ни второй строки, ни второй заявки.
$stmt->execute();
$stored = $stmt->affected_rows > 0;   // 0 — такое событие уже приходило

if (!$stored) {
    echo json_encode(array('status' => 'ok', 'message' => 'Duplicate event'));
    exit;
}

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

Как различить заявки нескольких сайтов в одной аналитике

Класть домен в заголовок заявки и в её поля. Если у компании несколько сайтов, они обычно пишут в один проект аналитики одним ключом счётчика и одним ключом вебхука, а заголовок заявки у всех одинаковый — «Новая заявка с чата». В списке заявок такие обращения неразличимы.

PHPЗаголовок и поле site отвечают на вопрос «с какого сайта пришли»
// Домен — в заголовок заявки и в поля.
// Это разделение по сайтам на уровне заявок; визиты им не разделишь.
$title = $leadEvents[$eventType] . ($domain !== '' ? ' — ' . $domain : '');

$payload = array(
    'title'         => $title,
    'roistat_visit' => $roistatVisit,
    'fields'        => array(
        'site'       => $domain,
        'event_type' => $eventType,
        'page'       => $page,
    ),
);

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

Куда девать переписку, если в аналитику её не отправить

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

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

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

  • Переписка с телефонами и почтой — персональные данные: доступ по сессии администратора либо по токену, каталог логов наружу отдаёт 403
  • Срок хранения задаётся явно и согласуется с владельцем данных; чистка идёт попутно при записи не чаще раза в сутки, если планировщика на хостинге нет
  • Соединение с базой на время своих запросов переключается в utf8mb4 — иначе эмодзи из чата приезжают вопросительными знаками
  • В выгрузке CSV значения, начинающиеся с =, +, - или @, предваряются апострофом, иначе таблица исполнит их как формулы

Что проверять после правки

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

  • На боевой странице отдаётся новая версия скрипта, а не закэшированная — при кэше статики в неделю правку приходится доставлять сменой версии у подключений, в том разборе на 679 страницах
  • В логе обработчика появилось событие с непустой меткой визита
  • В кабинете аналитики по этому обращению создан лид, и номер визита у него заполнен
  • Повторная отправка того же события заявку не дублирует
  • Обработчик без токена отдаёт 403, с токеном — 200
  • Перед заливкой у каждого правленого файла есть резервная копия на сервере, а откат описан одной строкой

Может ли чужой виджет остановить сбор заявок

Может, и без вашего участия. В день работ у сервиса чата истёк TLS-сертификат домена, с которого грузится виджет. Браузеры перестали загружать скрипт с ошибкой сертификата, файл при этом сервером отдавался — виджет исчез со всех сайтов, где он стоял, примерно на три часа, пока сервис не выпустил новый сертификат.

Обхода на своей стороне нет: домен и сертификат чужие. Что реально можно сделать — знать об этом раньше клиента.

  • Следить за сроком сертификата у внешних доменов, от которых зависит сбор заявок, а не только у своего
  • Ставить оповещение на тишину: лог обработчика не пополнялся N дней — повод проверить, а не радоваться
  • Держать адреса вебхуков в списке того, что проверяется после любого переезда

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

Если заявки не сходятся с аналитикой

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

Посмотреть услугу: маркетинг и SEO Обсудить задачу