lead tracking, contact forms, gtm, ivan jerkic, the marketing guy, splitx, one ivan

GTM: Lead Tracking and Contact Forms

Reading Time: 11 minutes

Table of Contents

Ivan Jerkić

Ivan Jerkić

I might know a thing or two about branding, performance marketing, content creation, product discovery & growth marketing, so to put it simply – I’m the marketing guy.

Over the years I’ve come across many problems that required a good old custom HTML tags to track something as intended. Even though Google Tag Manager has been updating the library of built-in variables, triggers, and tags pretty often, I still find some of the specific use-cases impossible to solve with built-in stuff.

I mostly work with WordPress sites, so the majority of the solutions written here will be centered around it. However, almost all of them will work without problems with any other CMS, because they’re not CMS-bound (albeit the specific ones like CF7 tracking that obviously only works with CF7, a WordPress plugin).

This series of posts is, as well, a sort of a homage to all amazing people that share their solutions online, and therefore save me tons of time I would otherwise spend to dig into coding. Of course, same as I needed to, you would have to adjust certain stuff to work out in your case — but that’s nothing hard or impossible to do.

Finally, the main reason why I’m writing now is that I’m tired of searching for custom snippets through bookmarks and GTM accounts. I want all the good stuff in one place, on my blog, easily accessible like some kind of GTM solutions library. I hope you’ll also find it that way.

Part one: lead tracking and contact forms

Update: I originally intended to fit everything into one blog post, but as I started writing I realized there was way too much content for one post. Therefore, I’ve decided to break it into 3 pieces focusing on three different subjects: contact forms and lead tracking, eCommerce, and website engagement. The other two should be out in the following weeks.

By the way, I will be updating all three parts as I encounter new challenges, or find and test different solutions for already mentioned use cases.

How to properly track WordPress Contact Form 7: three different solutions

Contact Form 7 is one of the most used WordPress plugins, so there is a great chance you will encounter a website using its backend for building forms. The plugin is very versatile, and there’s a bunch of 3rd party plugins for it, making it kinda “Inception”… a plugin for a plugin within a plugin.

This versatility opens up possibilities for different lead tracking approaches. I’ve used 3 of them so far. Which one will work best for you? I don’t know, but each one has its pros and cons.

1. Redirect to “Thank you page” on successful submission

Personally, I don’t like this method because some users tend to quit the website as soon as they send the form, leaving no time to load tags on a thank you page — especially on slower internet connections and therefore leaving your lead tracking efforts in vain.

However, it can be a good solution in case you have a WordPress admin login or a client has a cooperative webmaster. Also, it’s pretty damn good if you want to make an up-sell or offer extra content for users on the page itself. Basically, you need to set up the CF7 to redirect to the thank you page after successful submission. There you just need to fire a standard lead event tag and you’re done.

CF7 doesn’t redirect by itself, so you will either need a 3rd party plugin or manually add this piece of code (read instructions).

document.addEventListener( 'wpcf7mailsent', function( event ) {
  location = 'http://example.com/';
}, false );

Once you do that, the rest is easy to finish up via GTM. You set up a standard event tag and a page view trigger that only fires if the page URL equals to the one of thank you page. Also, you can differentiate forms by using different thank you pages for each one.

2. Track it via dataLayer event from Google Tag Manager for WordPress (by Thomas Geiger)

This is the fastest solution if you have the backend access to a website that you want to implement lead tracking into, and if there’s a need to track enhanced eCommerce events from WooCommerce — then it’s ideal. Why? Because this plugin setups dataLayer and integrates GTM with WordPress in just a few clicks and absolutely no coding knowledge required, plus it’s free.

Since the post is not about the plugin, I will link it here — there is plenty of documentation and discussions on forums. Also, I will write more about the WooCommerce part in a second blog post, so let’s concentrate on CF7 integration here.

The plugin has one-click integration with CF7. By integrating them you’ll automatically enable plugin’s CF7 DOM event listener that creates a custom event in dataLayer called gtm4wp.contactForm7Submitted once the form is submitted successfully. The event pushes form id and field values into dataLayer as well, which comes in handy when you have multiple contact forms or when you want to pull more variables into GTM for analytical purposes.

lead tracking, gtm, google tag manager, gtm custom event trigger

You can test it via the GTM preview feature or install the dataLayer checker extension for Google Chrome if it is already on a live website.

The only possible “problem” I’ve encountered so far with this type of lead tracking setup is the situation where you use a multi-step CF7 (plugin for CF7 that enables this option), as it actually acts like separate forms in the backend. In that case, you need to specify the URL where you want the trigger to work (for example, only on the last step of the form). However, every step of the form has its unique formID, which can be used to differentiate the steps in GTM tracking in the case when all steps have the same URL.

3. Custom GTM tag solution (my favorite)

Finally, my favorite, and very versatile way of lead tracking the Contact Form 7 submissions via a custom HTML tag, provided by Analytics mania.

CF7 developers have listed several DOM events on their website:

  • wpcf7invalid — Fires when an AJAX form submission has completed successfully, but mail hasn’t been sent because there are fields with invalid input.
  • wpcf7spam — Fires when an AJAX form submission has completed successfully, but mail hasn’t been sent because a possible spam activity has been detected.
  • wpcf7mailsent — Fires when an AJAX form submission has completed successfully, and mail has been sent.
  • wpcf7mailfailed — Fires when an AJAX form submission has completed successfully, but it has failed in sending mail.
  • wpcf7submit — Fires when an AJAX form submission has completed successfully, regardless of other incidents.

1st step — event listener:

The DOM event we need to track as a lead is wpcfmailsent, which fires on a successfully submitted form and follow up email. In order to do that, we need to create a custom HTLM tag in GTM that acts as the event listener. You can see the piece of code below:

document.addEventListener( 'wpcf7mailsent', function( event ) {
 "event" : "cf7submission",
 "formId" : event.detail.contactFormId,
 "response" : event.detail.inputs

This listener creates a dataLayer event cf7submission and an array of values containing form fields’ info such as email, phone number, name, etc. Also, don’t forget to set up the listener trigger only on pages where you have active contact forms. For example /contact or on all pages if you keep the form in the footer.

2nd step — custom event trigger:

In order to turn the dataLayer event into a GA event or Google Ads / Facebook Pixel lead you have to create a custom trigger (in this case cf7submission) and set it up like on the picture below.

gtm custom event, lead tracking

3rd step — pulling variables from dataLayer:

In order to properly pull variables from dataLayer, you can either use a dataLayer checker Chrome extension on a live site or simply run a site in GTM preview mode. The goal is the same in both cases. Below you can see an example on how to pull variables using the preview mode.

datalayer variables, lead tracking, gtm, google tag manager

To pull data from arrays you need to understand the hierarchy. In this case, the array name is response, and it contains objects numbered from 0 to 4 (in total 5 objects). Finally, the final key is value. So, for example, a dataLayer variable that will return us the user’s name is response.0.value. Each level of the data hierarchy needs to be separated by a dot.

In order to get users’ data from dataLayer to an analytical tool such as Google Analytics, you need to create user-defined variables in Google Tag Manager following the logic written above.

datalayer variable, gtm, lead tracking, form tracking

4th step — event / conversion tags and test

Finally, you need to create tags for Facebook Pixel standard lead event, or Google Ads lead conversion, or standard GA event tag (depending on what you want to use)… that will be triggered by a previously created custom event trigger cf7submission. You can play with the variables if you want to send dynamic data containing the user’s responses to Google Analytics. Don’t forget to test the tags in preview mode before publishing.

To sum it up, this method lets you play with code and tweak it to your needs. Because of that, and because of the fact I do not need any WordPress plugin to implement it, this is my preferred way of lead tracking Contact Form 7.

How to track any AJAX contact form

If your client’s site has AJAX-powered contact forms, you should use a custom HTML tag provided by Analytics mania. Following this method, you can basically track any AJAX form.

Disclaimer: I know that built-in form triggers can help you in certain cases as well, but in my case, I was never able to get anything useful from them. Built-in triggers cannot catch AJAX form submissions properly. However, AJAX listener did wonders in almost every case barring a few exceptions like Elementor form on which you’ll read a bit later.

Step one — AJAX event listener:

The first thing we need to do is to create an AJAX listener via a custom HTML tag. Below is the code snippet.

<script id="gtm-jq-ajax-listen" type="text/javascript">
 (function() {
 'use strict';
 var $;
 var n = 0;
 function init(n) {
 // Ensure jQuery is available before anything
 if (typeof jQuery !== 'undefined') {
 // Define our $ shortcut locally
 $ = jQuery;
 // Check for up to 10 seconds
 } else if (n < 20) {
 setTimeout(init, 500);
 function bindToAjax() {
 $(document).bind('ajaxComplete', function(evt, jqXhr, opts) {
 // Create a fake a element for magically simple URL parsing
 var fullUrl = document.createElement('a');
 fullUrl.href = opts.url;
 // IE9+ strips the leading slash from a.pathname because who wants to get home on time Friday anyways
 var pathname = fullUrl.pathname[0] === '/' ? fullUrl.pathname : '/' + fullUrl.pathname;
 // Manually remove the leading question mark, if there is one
 var queryString = fullUrl.search[0] === '?' ? fullUrl.search.slice(1) : fullUrl.search;
 // Turn our params and headers into objects for easier reference
 var queryParameters = objMap(queryString, '&', '=', true);
 var headers = objMap(jqXhr.getAllResponseHeaders(), 'n', ':');
 // Blindly push to the dataLayer because this fires within GTM
 'event': 'ajaxComplete',
 'attributes': {
 // Return empty strings to prevent accidental inheritance of old data
 'type': opts.type || '',
 'url': fullUrl.href || '',
 'queryParameters': queryParameters,
 'pathname': pathname || '',
 'hostname': fullUrl.hostname || '',
 'protocol': fullUrl.protocol || '',
 'fragment': fullUrl.hash || '',
 'statusCode': jqXhr.status || '',
 'statusText': jqXhr.statusText || '',
 'headers': headers,
 'timestamp': evt.timeStamp || '',
 'contentType': opts.contentType || '',
 // Defer to jQuery's handling of the response
 'response': (jqXhr.responseJSON || jqXhr.responseXML || jqXhr.responseText || '')
 function objMap(data, delim, spl, decode) {
 var obj = {};
 // If one of our parameters is missing, return an empty object
 if (!data || !delim || !spl) {
 return {};
 var arr = data.split(delim);
 var i;
 if (arr) {
 for (i = 0; i < arr.length; i++) {
 // If the decode flag is present, URL decode the set
 var item = decode ? decodeURIComponent(arr[i]) : arr[i];
 var pair = item.split(spl);
 var key = trim_(pair[0]);
 var value = trim_(pair[1]);
 if (key && value) {
 obj[key] = value;
 return obj;
 // Basic .trim() polyfill
 function trim_(str) {
 if (str) {
 return str.replace(/^[suFEFFxA0]+|[suFEFFxA0]+$/g, '');
 * v0.1.0
 * Created by the Google Analytics consultants at http://www.lunametrics.com
 * Written by @notdanwilkerson
 * Documentation: http://www.lunametrics.com/blog/2015/08/27/ajax-event-listener-google-tag-manager/
 * Licensed under the Creative Commons 4.0 Attribution Public License

Set the tag to fire on all pages, or on specific pages that contain contact forms — either is perfectly fine. I prefer the second option because it prevents the script from running on pages where it’s not really needed. Once everything is set up, the listener should create a dataLayer event named ajaxComplete every time AJAX is executed on a page, which should hopefully be triggered by a successfully sent contact form.

Open GTM preview mode and look for ajaxComplete event once you send a contact form. It should look something like this.

ajax listener gtm, gtm custom tag, lead tracking, form tracking

Open the event and try to find something that identifies a successfully submitted form. It’s usually a response value. It should contain something along with these words “This message has been successfully sent”. This value can be used as a trigger in lead tracking. Also, remember that this message will vary from website to website because it represents the thank you message once the form was sent that is easily customized.

Step two — dataLayer variables:

Before setting up the trigger, we need to define the new dataLayer variable: attributes.response using the same technique of data pulling from arrays that I explained earlier with Contact Form 7.

datalayer variable, lead tracking, ajax form tracking

After setting up the variable, run the preview mode to make sure it contains some values — in this case, it should contain “This message has been successfully sent”. If you cannot see any data, double-check the variable in GTM to make sure that you correctly referenced the variable in user-defined variables.

Step three — custom event trigger:

We need to create a custom event trigger for this to work. 

  • Event name — we need to pull the actual dataLayer event name here (from the first step): ajaxComplete.
  • Make it fire on some custom events.
  • Define the condition as variable attributes.response contains This message has been successfully sent. Note: this response/success message will probably be different in your case… maybe something like “Thank you for sending the form!

The trigger should look like this:

ajax complete trigger, ajax form tracking, gtm, lead tracking

One more thing… not every form is the same, so it might happen in some cases that the AJAX listener returns a bit different array than on examples above. Some forms might contain the response array instead of the response variable like in my example. In the case of an array, your user-defined dataLayer variable might be atttributes.response.message or something similar.

Step four — tags and testing:

Finally, once the trigger is created, you should assign it to any tag you find appropriate for the lead tracking like Google Analytics event, Google Ads lead conversion, Facebook Pixel lead, and so on.

How to track contact forms with CSS selectors (Elementor form)

Elementor is one of the most popular WordPress page and theme builders nowadays. Pro version brings its own form builder as well. Elementor forms usually just display a short “thank you notice” every time a form is successfully submitted. The aforementioned AJAX form lead tracking usually does not work with Elementor forms, but you can track them using Element Visibility triggers.

To make this trigger work, you’d need to obtain the CSS ID or CSS selector from Elementor’s form “thank you notice” which appears once the form is successfully submitted. In order to do that, you need to right-click the “thank you“ notice and select inspect element option. Check out if there’s any CSS ID present or CSS class. Elementor’s default classes for “thank you notice” are .elementor-message and .elementor-message-success. Your Element Visibility trigger should look like this:

element visibility trigger, elementor form tracking, lead tracking

There are a few important things to mention here:

  • Selection method needs to be CSS Selector if you’re targeting CSS classes, but it needs to be an ID if you’re targeting CSS IDs
  • If the “thank you notice” has more CSS classes defining its style, you need to write them in this format .css-class1.css-class2 — avoid spaces between classes, just use a dot. Default Elementor CSS classes for sent form notice should look like this: .elementor-message.elementor-message-success
  • Tick the “observe DOM changes” checkbox. This will track the appearance of the on-screen event dynamically, which is needed for Elementor forms that display the “thank you notice” after successful submission.
  • Make the trigger fire only once per page if there is only one form per page.

Finally, test the trigger in the preview mode. If it does not appear, double-check the CSS class(es) name(s) to make sure you’ve written them properly in GTM, as that’s the most common issue. If everything works well, connect the trigger to usual lead tracking tags (Google Analytics event, Google Ads conversion, etc.)

Final words and future updates

As I said in the beginning, I am determined to make this post series a sort of personal GTM library. I will update it as I come across other solutions that prove to be useful for my clients. So, you know what to do, bookmark this lead tracking article, and the other two on eCommerce and website engagement and take a look from time to time. Also, if you want to reach out, I’m always available via https://oneivan.com/contact/.

Share on facebook
Share on twitter
Share on linkedin
Share on whatsapp
Share on telegram
Share on reddit
Share on vk
Share on pocket
Share on email
Share on print

Latest posts

purpose of marketing, marketing, ivan jerkic, the marketing guy, splitx, one ivan
Brand Marketing
Ivan Jerkić

What’s the purpose of marketing?

Reading Time: 2 minutes The purpose of marketing is to listen, to see, and to create accordingly. Start from the audience, not a product. Start with “you” instead of “us”. Sometimes it’s necessary to step back and ask ourselves “Why are we doing this in the first place?”

Read more
brand's tone, brand's voice, ivan jerkic, the marketing guy, splitx, one ivan
Brand Marketing
Ivan Jerkić

How to find your brand’s tone and voice?

Reading Time: 3 minutes Branding goes way more than choosing a logotype and color scheme. A brand is the soul of a product. A product without a brand is nothing more than a commodity. Easily acquired, easily replaced, easily forgotten.

Read more