Захоплення і прокидання UTM з landing

https://besocial.studio · https://besocial.studio/shift2 — фронт-логіка на besocial.studio

Суть

Лід заходить на besocial.studio за рекламним посиланням з UTM-параметрами в query string. Лендінг зберігає усі параметри в localStorage і прокидає їх далі у дві точки: при кліку на Telegram-бот @BeSocialHR_bot і при сабміті контактної форми. Це єдиний шлях, яким UTM рекламних кампаній доходять до stack-беку.

Use cases

  • Лід приходить з Facebook/TikTok-реклами → клікає кнопку «Пройти співбесіду з HR-котом» → попадає у Telegram-бот вже з UTM.
  • Лід заповнює форму «залиш свої контакти» (зараз прихована, display:none, лишається як заглушка) → рекрутер отримує заявку з UTM-контекстом.

Захоплення параметрів

На DOMContentLoaded:

  1. Читаємо усі query-параметри через URLSearchParams — без whitelist, generic key/value. Усе що в URL після ? потрапляє в payload.
  2. Додаємо userAgentnavigator.userAgent || navigator.vendor || window.opera.
  3. Merge у localStorage['urlParams'] (ключ — urlParams, значення — JSON-об’єкт). Новий захід зливається з попереднім: нові значення перезаписують старі, відсутні не обнуляються — параметри накопичуються між візитами.

Whitelist навмисно не використовується — бек сам відбирає потрібні поля. Тобто gclid, t, або будь-яке нестандартне значення з URL так само потрапить у сесію.

Прокидання → Telegram бот

  1. Відправляється подія FB Pixel конверсія fbq('track', 'ViewContent') (спрацює тільки якщо Pixel ініціалізований — див. trackers).
  2. Щоб обійти нюанс обмеження query параметра при /start telegram бота (ліміт 64 символа!!!), була добавленна логіка сесій. При кліку на старт бота - ми відправляємо на сайт utm мітки, отримуємо sessionId.
  • POST https://api.besocial.tech/lead-session/createSession з body = вмісту localStorage['urlParams'].
    • Якщо urlParams порожній — запит не відправляється взагалі, sessionId = null.
  1. Редірект на https://t.me/BeSocialHR_bot?start=<sessionId>. Якщо sessionId нема — на голий https://t.me/BeSocialHR_bot без параметра start.

Прокидання → контактна форма

Зараз форма прихована (#contact має display:none). Код лишається як заглушка для майбутнього сценарію — якщо вмикнути блок, логіка нижче працює одразу.

  1. Валідація трьох полів:
    • name — не порожнє, ≥ 2 символи після trim().
    • phoneNumber — regex /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/ (попередньо вирізаються пробіли).
    • telegram — regex /^@?[a-zA-Z0-9_]{5,32}$/ (опційний @).
  2. Сабміт: POST https://api.besocial.tech/recruit/operatorCard/createOperatorBesocialStudioForm з body { name, phoneNumber, telegram, params }, де params — вміст localStorage['urlParams'].
  3. Успіх — форма ховається, показується блок #form-success. Помилка — показується #form-error, кнопка розблоковується для повторного сабміту.

Нюанси

  • Накопичення в localStorage. Якщо лід заходив двічі з різних кампаній — параметри злиті, переможе останнє значення по кожному ключу.
  • sessionId одноразовий. Бек видаляє сесію одразу після того як бот її прочитав за /start <sessionId>.
  • Атрибуція ламається при «переході через буфер». Якщо лід зайшов на besocial.studio, скопіював посилання на бот вручну і відкрив у іншому пристрої — UTM втрачаються.
  • pixel_id як UTM. pixel_id потрапляє в localStorage нарівні з іншими параметрами і прокидається в createSession — він потрібен не тільки для client-side FB Pixel, а й для server-side подій (див. Facebook Pixel — динамічна логіка).
  • Дублі лендінгу (shift1 / shift2). Логіка ідентична. Якщо змінюєш — синхронізуй обидва файли. Спільної бібліотеки нема, скрипти живуть інлайном у <script> блоках.