Skip to main content

widget-utm-collector.js — универсальный сбор UTM/click-id для форм GetCourse

widget-utm-collector.js — универсальный сбор UTM/click-id меток в форму GetCourse

Скрипт-заменаСкрипт, длякоторый автоматически прокидывает рекламные метки (UTM + click-id) из URL и из first-touch storage в скрытые поля любой формы GetCourse. Работает с любыми виджетами и лендингами — не только с квизом Fitness Mama.

Заменяет vakas-tools utm_to_gk_field.js, оту vakas-tools.которого Используетсяесть втри виджетекритичных формыбага 1602182для насовременных страницелендингов квиза(см. gc.fitnessmama.co/quizниже).

Зачем заменилинужен (vs vakas)

Проблема vakas

В

widget-utm-collector скрипте vakas естьдвакритичных недостатка для нашей конфигурации:
    Слушает DOMContentLoaded — если виджет загружается позже, который к моменту загрузки виджета уже произошёл (виджетчерез формыинжект инжектится поздно,скрипта на email-шагедействие квиза).пользователя), Listener регистрируется, ноlistener не срабатываетсработает readyState-чек: поляотрабатывает остаютсяв пустыми.любой момент после загрузки Захватывает только utm_*fbclid, /gclid, /yclid игнорирует.теряются РекламаЛовит безвсе явных9 UTMпараметров приходит безатрибуции. Нет first-touch: при возврате на сайт по закладкезакладке/прямому или прямом заходезаходу метки теряются.потеряны Сохраняет

    Из-за этого в выгрузке deal_export.xlsx много сделок без меток, хотя пользователи приходили с рекламы.

    Что делает новый скрипт

      Читает из URL: utm_source, utm_medium, utm_campaign, utm_content, utm_term, utm_group, fbclid, gclid, yclid. На первом заходе сохраняет ихfirst-touch в localStorage (ключ fm_first_touch, срокна 90 дней)дней first-touchатрибуция.Заполняет Если в текущем URL меток нет — подтягивает их изтолько localStorage<input>. Заполняет <input> и <textarea> внутри элементовформыНе сдиспатчит CSS-классами, совпадающими с именами параметров: .utm_source, .utm_group, .fbclid и т.д. Использует readyState-чексобытияотрабатываетGC дажеможет еслиигнорировать DOMContentLoadedзначение уже произошёл (главный фикс). Диспатчит события input и+ change нужнодляОдна корректнойпопытка отправкизаполнения полей GetCourse'ом. Делает триТри попытки заполнения (0мс, 500мс, 1500мс) для напоздно случайотрисованных позднейполей дорисовки виджета.

      Где лежит и как подключёнскрипт

      Файл скрипта хостится в CMS GetCourse:GetCourse (раздаётся с того же домена gc.fitnessmama.co — нет CORS, нет задержек):

      https://gc.fitnessmama.co/pl/cms/layout/js?id=92493&hash=abc932387ed49dc892bf87e6f0fa586d&bundle=1
      

      (hash пересчитывается автоматически при изменении файла в CMS — кэш сбрасывается сам.)

      ПодключениеИсходник в проекте: widget-utm-collector.js (в репозитории квиза).


      Универсальная настройкадля любого виджета и лендинга

      Чтобы подключить скрипт к новой форме / новому лендингу, нужно 3 шага.

      Шаг 1 — Добавить скрытые поля в виджетеформу GetCourse

      В настройках виджета формы 1602182добавьте вOffer HTML-блоке:

      field
      <scriptили src="https://gc.fitnessmama.co/pl/cms/layout/js?id=92493&hash=abc932387ed49dc892bf87e6f0fa586d&bundle=1"></script>User 
      field

      (по одному на каждую метку, которую хотите сохранять). Какие поляименно — зависит от того, какие данные нужны в форме

      сделке/контакте.

      ЧтобыМинимальный скрипт знал, куда писать значения, в форме виджета должны быть полянабор (User field или Offer field) со следующими CSS-классами:рекомендую):

      CSS-класс поля ЧтоКакое попадётзначение запишется
      Куда сохранять (поле в GC) utm_source utm_source deal_utm_source или user_utm_source utm_medium utm_medium deal_utm_medium utm_campaign utm_campaign deal_utm_campaign utm_content utm_content deal_utm_content utm_term utm_term deal_utm_term

      Расширенный набор (если ведёте таргет Facebook/Google/Yandex):

      utm_groupCSS-класс utm_group (URL offer-страницы с ответами квиза)Значение fbclid Facebook click ID gclid Google click ID yclid Yandex click ID utm_group Кастомный параметр (мы используем для URL квиза с ответами)

      ИменаВажно:

      самих
      полейВ каждом поле должна стоять галочка «hidden» (скрытое от пользователя). Имя самого поля в GetCourseGC (например deal_utm_source или offer_quiz_url) — на ваше усмотрение.усмотрение, Важенскрипт работает по CSS-классу, а не по имени поля. CSS-класс пишется в раздел «CSS element class» в настройках поля. Можно к одному классу написать несколько слов через пробел: utm_source hidden — скрипт смотрит только на обёрткеutm_source. поля.

      Шаг 2 — Подключить скрипт внутри виджета

      В настройках виджета формы добавьте HTML-блок (или используйте существующий) и вставьте:

      <script src="https://gc.fitnessmama.co/pl/cms/layout/js?id=92493&hash=abc932387ed49dc892bf87e6f0fa586d&bundle=1"></script>
      

      Расположение HTML-блока в форме не важно — лучше в конце, чтобы поля успели отрисоваться к моменту запуска. Но скрипт всё равно перезапустится трижды с задержкой, так что критичности нет.

      Шаг 3 — Тестирование

        Откройте страницу с формой в инкогнито с параметрами: https://ваш-домен/страница?utm_source=TEST&utm_campaign=PROOF&fbclid=ABC123 F12 → Console. В Console проверьте, что поля заполнены:
        document.querySelectorAll('.utm_source input, .utm_campaign input, .fbclid input').forEach(el => {
          console.log(el.name || '(no name)', '=', JSON.stringify(el.value));
        });
        
        Сабмитьте форму. В новой сделке/контакте в GetCourse метки должны появиться.

        СвязкаДополнительно содля страницейлендингов квизас многошаговой логикой

        Если лендинг сложный (квиз, многоэкранник, SPA) — форма может появляться через минуты после загрузки страницы, и URL к этому моменту может быть уже изменён (history.pushState/replaceState). В этом случае нужно продублировать first-touch логику на самом лендинге, чтобы метки сохранились в localStorage ещё при первой загрузке.

        Минимальный код для лендинга

        Вставьте в <head> или в начало <body> лендинга:

        <script>
        (function(){
          var TRACK = ['utm_source','utm_medium','utm_campaign','utm_content','utm_term','utm_group','fbclid','gclid','yclid'];
          var KEY = 'fm_first_touch';
          var TTL = 90 * 86400000;
          var sp = new URLSearchParams(location.search || '');
          var data = {}, any = false;
          TRACK.forEach(function(k){ var v = sp.get(k); if (v){ data[k] = v; any = true; }});
          if (any) {
            try {
              if (!localStorage.getItem(KEY)) {
                localStorage.setItem(KEY, JSON.stringify({ts: Date.now(), data: data}));
              }
            } catch(e){}
          }
        })();
        </script>
        

        Это «отщёлкивает» метки в storage сразу при первой загрузке, до того как сайт начнёт менять URL или пользователь перейдёт на другую страницу.

        widget-utm-collector.js потом достанет их из того же localStorage ключа.

        Если на лендинге есть редирект на форму с параметрами

        Когда лендинг строит URL для перехода на форму (как наш quiz-pink.gc.html)html

        На сторонеoffer.html), квизаважно работаетпротащить параллельнаяметки логикачерез query string. Пример паттерна — функция buildOfferUrl() в quiz-pink.gc.htmlhtml:1185:

        // собираем все метки из URL/storage
        const INCOMING_UTM = /* ... читаем url + localStorage ... */;
        
        function buildOfferUrl() {
          const params = new URLSearchParams();
          // ... данные с лендинга ...
        
          // и в конце — дописываем метки
          for (const [k, v] of INCOMING_UTM) {
            params.set(k, v);
          }
          return OFFER_URL + '?' + params.toString();
        }
        

        Тогда форма получит метки и через URL (быстрый путь), и через localStorage (резервный).


        Где НЕ сработает (фундаментальные ограничения)

        Никакой клиентский скрипт не решит:

        • INCOMING_UTM при загрузке страницы тоже захватывает все параметры из списка TRACK_PARAMS.
        Сохраняет first-touch в тот же localStorage ключ fm_first_touch (90 дней). buildOfferUrl() дописывает все эти параметры в URL формы → они доступны виджету.

        Таким образом метки переживают: переходы между страницами квиза, F5, history.replaceState, возврат по закладке.

        Чего скрипт НЕ решает

          Прямой / Прямой/органический / referral трафик без меток в URL и без сохранённого first-touch — нечего ловить. Это нормально, такие лиды атрибутируются как organic/direct. ИзменениеИнкогнито-режим структуры формыlocalStorage стирается при закрытии вкладки. Запрещённые cookies/storage: если у поля в форме отсутствует CSS-класс из списка — скрипт егомолча игнорирует. Кросс-девайс (зашёл с телефона, купил с ноута) — localStorage привязан к устройству. Прошло >90 дней с первого захода — TTL истёк. JS отключён — скрипт не заполнит. Проверяй CSS-класс при добавлении новых полей.выполнится.

          Для перекрытия этих случаев нужна серверная атрибуция (Facebook CAPI, Google Enhanced Conversions, GTM Server-side) — это отдельная задача, не для этого скрипта.

          Реалистичный охват для рекламного трафика с UTM: 90-95% сделок будут с метками.


          Как обновлять скрипт

          1. Зайти в GetCourse → CMS → найти JS-слой с id 92493.92493.
          2. Внести правки.правки в код.
          3. Сохранить — GC автоматически пересчитает hash в URL и браузеры подтянут новую версию.подключения.
          4. Если hash по какой-то причине небраузер поменялсякеширует старую версию — добавить в URL &v=2 в(или URLподнять подключенияномер) для сбросапринудительного кэша.сброса.

          Как протестировать

            Открыть в инкогнито: https://gc.fitnessmama.co/quiz?utm_source=TEST&utm_campaign=PROOF&fbclid=ABC123 F12 → Console. Пройти квиз до email-шага (быстрый jump через консоль):
            Object.assign(answers, {name:'Test', currentWeight:'70', targetWeight:'60', height:'165'});
            cur = STEPS.findIndex(s => s.type === 'email');
            render(cur, 'forward');
            
            Через пару секунд проверить, что скрытые поля заполнились:
            document.querySelectorAll('.utm_source input, .utm_campaign input, .fbclid input, .utm_group input, .utm_group textarea').forEach(el => {
              console.log(el.tagName, 'name=', el.name || '(no name)', 'value=', JSON.stringify(el.value));
            });
            
            Сабмитнуть форму. Проверить в новой сделке в GetCourse, что метки записались в соответствующие поля.

            Что делать, если метки не пишутся

            Чек-лист по убыванию вероятности:

            1. Проверить, что скрипт загружен: вNetwork Network найти запрос pl/cms/layout/js?id=92493 статус 200, в Response виден код.
            2. Проверить first-touch в localStorage:touch: в Console → localStorage.getItem('fm_first_touch') должен быть JSON с ts и data (если пользователь приходил с UTM).
            3. Проверить наличие полей с нужными классами: в Console → document.querySelectorAll('.utm_source, .utm_group'utm_campaign').length большедолжно быть > 0.
            4. Проверить, что класс стоит на правильном элементе: класс должен бытьстоять на обёртке поля (Offer field / field/User field в билдере GC)field), а внутри неё уже находится <input> или <textarea>.
            5. Проверить, что скрипт реально отработал: Console → document.querySelector('.utm_source input').value → не пусто.
            Если на лендинге URL подменяется — убедиться, что first-touch код из секции «Дополнительно для лендингов» вставлен до любых history.replaceState/pushState.

            Конкретные применения

            Где Виджет Лендинг Статус Квиз Fitness Mama Форма 1602182 gc.fitnessmama.co/quiz ✅ работает с 2026-05-18

            При добавлении новых форм/лендингов — обновите эту таблицу.


            История изменений

            • 2026-05-18 — первая версия. Замена vakas-tools/utm_to_gk_field.js.
              • ДобавленreadyState-чек readyState-чек.(исправляет неотработку при поздней загрузке виджета).
              • Добавлена поддержкаПоддержка fbclid/gclid/yclid.
              • ДобавленFirst-touch first-touchв localStorage на 90 дней в localStorage.дней.
              • Заполнение <textarea> в дополнение к <input> (для длинных полей вроде offer_quiz_url).
              • Диспатч событий input/change.
              после
            • Три установкипопытки значения.заполнения с задержкой.