Отслеживание заявок - одна из тех задач, благодаря которым веб-аналитика вообще существует. Иначе ведь и не понять, что реклама работает. А как известно, без Adwords не было бы Analytics.
Несмотря на всю важность точного отслеживания форм, универсального решения нет и быть не может, поэтому каждый раз приходится выдумывать или вспоминать, что еще можно сделать в конкретной ситуации, а это не всегда удается, а проджекту надо ASAP, а у тебя еще очередь да и на обед давно не ходил, с другой стороны интересно же, хотя делал кучу раз, клиент зато хороший, но сайт ужасный, а в контейнере вообще треш... короче я всё записал!
Так что палец на колесо, и покрутили от простого к менее простому.

Эксперимента ради я добавил в этот пост интерактивности: на странице стоит отдельный контейнер GTM, все формы обрабатываются описанными способами, результат видно, если нажать F12 в Chrome, далее вкладка Network и фильтр по слову collect.
Вот выгрузка контейнера, которую можно поставить к себе и подключить через Injector. Короче, всё к услугам читателя, развлекайся, дорогой гость!
Содержание
Встроенный триггер
Do you feel lucky, punk?
-- Clint Eastwood, Dirty Harry
Первое, что стоит попробовать - встроенный способ отслеживания. По моему опыту он срабатывает довольно редко, но тем не менее.

Этот триггер прослушивает Submit форм и при наступлении оных передает в dataLayer событие gtm.formSubmit. По выполнении сабмита можно, например, отправить событие в Google Analytics.
Разберем доступные настройки триггера:
Ждать теги - параметр будет полезен, если читатель возжелает к отправке формы подвязать уведомления CPA-сетей или что еще похуже, но переусердствовать с ним конечно не стоит.
Проверка ошибок воспрепятствует срабатыванию, если при отправке формы страница не перезагружается, что случается довольно часто, поэтому я оставил её выключенной. (Отсутствие перезагрузки означает, что выполнена функция
preventDefault(), отменяющая действие по умолчанию)Условия активации нужны, чтобы читатель указал, какая именно форма его интересует.
На этой странице стоит отдельный контейнер Google Tag Manager с реализацией всех описанных методов, так что, если отправить тестовую форму ниже, то в заранее открытой вкладке Network читатель увидит ушедший запрос к collect.
Встроенный триггер
ТЗ для разработчиков
Когда встроенный триггер не сработал, и нет желания копаться в коде, на помощь приходит простой для веб-аналитика, но зачастую сложный с административной точки зрения способ.
"Пусть этот криворукий рукожоп сам ковыряется" - думает читатель и направляет разработчику ТЗ с требованием при отправке формы выполнять примерно следующий javascript код:
window.dataLayer = window.dataLayer || []
window.dataLayer.push({
event: 'form__tz',
details: {
formID: '{{идентификатор формы}}',
contact: '{{оставленный контакт}}',
},
eventCallback: function () {
// обработчик формы
onFormSubmit()
},
})
В данном случае тег с отправкой в Google Analytics нужно привязать на пользовательское событие form__tz. Разбираем написанное:
detailsпредлагает разработчику положить туда некоторую информацию о форме, которую можно достать через Переменную уровня данных второй версии. Для идентификатора -details.formID, а вот контакты по законам Google нельзя передавать в Analytics, но можно в Spreadsheets или в TelegrameventCallbackаналогичен свойству Ждать теги. Он нужен, чтобы поместить туда обработчик формы, который отправит заявку в CRM или на почту, но случится это только после всего остального, что завязано на отправленное событиеform__tz. Использование не обязательно.
Проверяем:
dataLayer.push()
Сообщение об успехе
Еще один довольно простой способ - завязаться на сообщение об успешной отправке формы. Это может быть страница (устар. варианты - Success Page, Thank you page или вообще TYP), на которую пользователя перекидывают после успешной отправки, типа ../success.php или как в Битриксе ../?WEB_FORM_ID=123&RESULT_ID=.. В таком случае триггер завязывается на посещение этой страницы, или же она сразу указывается в цели в Google Analytics.
Минус в том, что пользователь может посетить эту страницу не только после отправления заявки, но и вернуться к ней во вкладке, добавить в избранное, воспользоваться подсказкой браузера, дать ссылку кому-то или вообще запостить её для всех. Очевидно, в этом случае читатель будет получать некорректные срабатывания.
Сообщение об успешной отправке, однако, может появляться и в виде модального окна без каких-либо перезагрузок. В этом случае поможет встроенный триггер типа Доступность элемента, который сработает при появлении сообщения, и отправит в dataLayer событие gtm.elementVisibility.

Триггер чудесный, решает множество задач, особенно в обремененных React'ом веб-приложениях, но пробежимся по нужным свойствам:
Метод выбора и Идентификатор элемента помогут указать на конкретный элемент, чьё появление нас интересует
Правило запуска этого триггера определяется задачей читателя. Если на странице несколько форм (стандартная + коллбэк), по которым всплывает одно и то же сообщение, то хорошо бы поставить как на картинке.
Регистрация изменений DOM поможет, если изначально элемента в структуре документа не было, и он встраивается в процессе. (React и прочие хипстерские технологии оперируют с виртуальным DOM и рендерят его по запросу, поэтому структура может легко меняться в процессе без особых усилий со стороны браузера)
Условия активации предлагают указать нужную страницу, и не нагружать клиентский ЭВМ ненужными операциями. В моем случае контейнер стоит только на одной странице, так что мне необязательно.
Success message
Прослушивание AJAX запроса
Случается, что вы сабмитите-сабмитите форму, а стандартный триггер не работает и в dataLayer ничего не отправляется.
Вполне возможно, что разработчик прервал
всплытие события, добавив stopPropagation() или return false в обработчике submit. Можно пожаловаться разрабу и попросить убрать, а можно вклиниться в процесс обработки заявки и перехватить её на следующем этапе путешествия - при отправке в CRM. Для этого придется переопределить метод send, используемый для отправки запроса, но делать это нужно с большой осторожностью!
Создаем тег типа Пользовательский HTML с кодом ниже, в триггере просмотр страницы с формой.
<script>
(function(open) {
XMLHttpRequest.prototype.open = function(method, url) {
this.responseURL = url // полифилл из-за IE
this.addEventListener('load', function() {
if (~this.responseURL.indexOf('/priemy-raboty-v-bigquery/')) {
dataLayer.push({
event: 'form__ajax',
details: {
// ...
},
})
}
})
return open.apply(this, arguments)
}
})(XMLHttpRequest.prototype.open)
</script>
Узнать, куда уходит запрос с нужной формы, можно с помощью console.log(this.responseURL), и этот url потом зафильтровать. В примере я отправляю данные формы с помощью FormData().
AJAX request
Подмена функции-обработчика
Если перехват запроса срабатывает постоянно, показывая один и тот же url, и в целом не помогает, то есть еще вариант с подменой обработчика этой формы. Найти его можно, посмотрев в консоли прослушку событий:

Или через Search all files по id\классу\имени. Форму в этом разделе обрабатывает функция process().
Делаем тег типа Пользовательский HTML с кодом ниже, в триггере Модель DOM готова на страницы страницы с формой.
<script>
// сохранение старой функции
var _old_process = process
// определение новой
process = function (form) {
dataLayer.push({
event: 'form__rewrite',
details: {
contact: form.querySelector('input[placeholder=Контакт]').value,
formID: form.id,
},
})
// вызов старой с теми же параметрами
_old_process.apply(window, arguments)
}
</script>
В данном примере способ кажется обманчиво простым, на практике же форма может передаваться сперва в валидатор, оттуда еще в функцию, запрятанную внутрь jQuery, куда читатель никак не попадет. Тем не менее проверяем: