FreeGoogle Tag Manager recipes

Track HubSpot Form Submissions in Google Tag Manager with this Free GTM Recipe

A free Google Tag Manager recipe that detects successful HubSpot Forms submissions and gives you a trigger for firing conversions to Google Ads, Google Analytics and the rest.

Aaron Beashel
Aaron BeashelUpdated September 20267 min read
The HubSpot Forms recipe tag inside Google Tag Manager

Google Tag Manager is a common way to send a conversion to Google Ads, Google Analytics, etc whenever a visitor fills in a HubSpot form on their site.

But it's surprisingly hard to get right. HubSpot renders its forms inside an embedded frame (an iframe) served from HubSpot's own servers, so Google Tag Manager's built-in form submission trigger doesn't work. And on top of that, most of the tracking code you'll find online listens for a message called 'onFormSubmitted' that forms built in HubSpot's newer form editor never send, so it doesn't work either.

So what's the solution?

Just import this HubSpot Forms Conversion Tracking Recipe into your Google Tag Manager account, and you'll have all the pieces you need for basic conversion tracking in a few minutes.

What Is a Google Tag Manager Recipe and What's in It?

Think of a recipe as a pre-packaged set of Google Tag Manager assets (a tag, a trigger and a variable) that you can import into your account to save you from assembling it piece by piece.

This recipe listens for successful HubSpot form submissions, passes a 'Form Submitted' event to Google Tag Manager, and gives you a Trigger you can use to send conversions on to Google Analytics, Google Ads and more. Below is everything each piece contains, so you know precisely what you're adding before you import it:

Custom HTML Tag

Waits for the success signal HubSpot sends once it accepts a submission, then pushes a 'hubspot_form_submitted' event to the dataLayer. It fires on All Pages, once per event, and this is the code inside it:

The Tag's CodeJavaScript · 101 lines
(function () {
  if (window.__converlyRecipeHubspotForms) return;
  window.__converlyRecipeHubspotForms = true;
  window.dataLayer = window.dataLayer || [];

  // One submission, one push. Keyed on the form id, so a duplicate signal for
  // the same submission inside 2 seconds is ignored, while a genuinely new
  // submission (which takes far longer than 2 seconds) still fires.
  var DEDUPE_WINDOW_MS = 2000;
  var lastFiredAt = {};

  function fireConversion(formId) {
    var id = formId ? String(formId) : '';
    var key = id || 'unknown';
    var now = Date.now();
    if (lastFiredAt[key] && now - lastFiredAt[key] < DEDUPE_WINDOW_MS) return;
    lastFiredAt[key] = now;
    window.dataLayer.push({
      event: 'hubspot_form_submitted',
      form_id: id
    });
  }

  // ------------------------------------------------------------
  // Path 1: current forms editor (live-verified)
  // ------------------------------------------------------------
  // HubSpot dispatches this on the .hs-form-frame container and lets it bubble
  // (or on document itself when it cannot find the container), with
  // detail = { formId, instanceId }. It only ever follows a submission
  // HubSpot's server accepted. Listening on document in the capture phase
  // catches both dispatch points and cannot be silenced by page code that
  // stops the event's propagation further down.
  function hubspotFormsApi() {
    return window.HubSpotFormsV4 || window.HubspotFormsV4 || null;
  }

  function handleSubmissionSuccess(event) {
    try {
      var detail = event && event.detail;
      if (!detail || typeof detail.formId !== 'string' || !detail.formId) return;
      if (typeof detail.instanceId !== 'string' || !detail.instanceId) return;
      // When HubSpot's forms API is on the page, the event must name a form
      // instance HubSpot actually rendered here. A stray or malformed event
      // with a made-up instance id is ignored.
      var api = hubspotFormsApi();
      if (api && typeof api.getFormFromEvent === 'function') {
        if (!api.getFormFromEvent(event)) return;
      }
      fireConversion(detail.formId);
    } catch (e) {
      // Never throw back into HubSpot's own event dispatch.
    }
  }

  // ------------------------------------------------------------
  // Path 2: legacy forms editor (ported from the production module)
  // ------------------------------------------------------------
  // Legacy forms announce each lifecycle step with a postMessage of
  // { type: 'hsFormCallback', eventName, id, data }. Only 'onFormSubmitted'
  // means the submission was accepted; 'onFormSubmit' also fires for
  // submissions that then fail validation, so it is ignored.
  //
  // Origin check: an inline legacy form broadcasts from the page's own window,
  // and an iframed one from a HubSpot-owned https origin. Anything else (an ad
  // iframe, another site) is rejected. Hosts are anchored so a lookalike such
  // as "hsforms.com.attacker.example" cannot pass.
  var HUBSPOT_ORIGIN_PATTERNS = [
    /(^|\.)hsforms\.com$/,
    /(^|\.)hsforms\.net$/,
    /(^|\.)hubspot\.com$/
  ];

  function isTrustedOrigin(event) {
    if (!event) return false;
    if (event.source === window && event.origin === window.location.origin) return true;
    if (!event.source) return false;
    if (!event.origin || typeof event.origin !== 'string') return false;
    var hostMatch = /^https:\/\/([^/:]+)(:\d+)?$/.exec(event.origin);
    if (!hostMatch) return false;
    for (var i = 0; i < HUBSPOT_ORIGIN_PATTERNS.length; i++) {
      if (HUBSPOT_ORIGIN_PATTERNS[i].test(hostMatch[1])) return true;
    }
    return false;
  }

  function handleMessage(event) {
    try {
      var data = event && event.data;
      if (!data || typeof data !== 'object') return;
      if (data.type !== 'hsFormCallback') return;
      if (data.eventName !== 'onFormSubmitted') return;
      if (!isTrustedOrigin(event)) return;
      fireConversion(typeof data.id === 'string' ? data.id : '');
    } catch (e) {
      // Never throw back into the page's own message handling.
    }
  }

  document.addEventListener('hs-form-event:on-submission:success', handleSubmissionSuccess, true);
  window.addEventListener('message', handleMessage, false);
})();

Variable

Holds the ID of the HubSpot form that was submitted, so any tag you want to fire can use it as a filter. These are its exact settings:

Name

DLV - HubSpot Form ID

Variable Type

Data Layer Variable

Data Layer Variable Name

form_id

Trigger

Fires as soon as the 'hubspot_form_submitted' event comes through. You can use it as the Trigger for any of your conversion Tags (e.g. GA4 event, Google Ads Conversion, etc). These are its exact settings:

Name

CE - HubSpot Form Submitted

Trigger Type

Custom Event

Event Name

hubspot_form_submitted

Download This Recipe

Download the file and import it straight into Google Tag Manager. The tag, variable and trigger you just read through all come pre-built, so there's nothing to assemble yourself.

Built from Converly's own HubSpot Forms detection code

Why It's the Best Recipe Out There

It is built from the same detection code we use in our own conversion tracking product, Converly, just adapted to run inside Google Tag Manager.

Works with HubSpot's new form builder as well as their legacy form tool (and the different embed codes those tools give you to place the form on your site).
Only fires after HubSpot's server accepts the submission. A visitor who leaves a required field empty, or whose entry HubSpot rejects (because of an invalid email address, for instance), is never counted as a conversion.
Put through the same QA process as our own conversion tracking product, and has been used to detect thousands of HubSpot form submissions for Converly customers.

How to Use the HubSpot Forms GTM Recipe

1

Install the Recipe

Download the recipe with the form above, then import it into your Google Tag Manager container. Inside GTM, go to Admin, click Import Container, choose the file you downloaded, and select the Merge option so nothing already in your container gets overwritten. This will add the listener, variable and trigger to your account for you.

Importing the recipe container in Google Tag Manager
2

Configure Your Tags

With the recipe in place, you can use it as the Trigger for any of your conversion tags. Here are two of the most common setups.

Example 1: Google Analytics

Set up a GA4 event tag and choose the recipe's trigger (named CE - HubSpot Form Submitted) as the trigger the tag fires on. From then on, every successful HubSpot form submission sends an event to Google Analytics.

A GA4 event tag using the recipe's trigger

Example 2: Google Ads

Create your Google Ads conversion tag and set it to fire on the recipe's trigger (named CE - HubSpot Form Submitted), so each successful HubSpot form submission is reported against your campaigns as a conversion.

A Google Ads conversion tag using the recipe's trigger

What This Enables You to Do (and What It Doesn't)

Each time HubSpot accepts a form submission on your site, this recipe sends Google Tag Manager an event. You can use that event as the trigger for your Google Ads, Google Analytics or other conversion tags.

What it does

Sends an Event to GTM

Catches successful HubSpot form submissions and automatically sends an event called 'hubspot_form_submitted' to your dataLayer.

Tells You Which Form Fired

Grabs the ID of the HubSpot form that was submitted and sends it along with the 'hubspot_form_submitted' event, so a site running several HubSpot forms can fire different tags for each one.

Creates a Trigger in GTM

Adds a ready-made 'Trigger' to Google Tag Manager that you can use to fire any of your conversion tags.

What it doesn't do

Doesn't send the conversion server-side

It all runs client-side, in the visitor's browser, so an ad blocker can stop the tag from loading and browser privacy rules can cut tracking down to as little as a day.

No Click IDs or Identifiers

It doesn't collect the Google click ID, Facebook click ID, Meta pixel cookies, IP address or user agent, which Google needs for Enhanced Conversions and Meta needs for a strong Event Match Quality score.

No Personal Details

The script only sends an event. It never sends the lead's name, email or phone number (which ad platforms like Google and Meta need for Enhanced Conversions or a high Event Match Quality score).

No Logging or Notifications

You can use Tag Assistant to confirm it works on the day you install it, but unless you recheck it every week, you won't know if it stops working.

The Proper Way to Send Conversions to Google Ads, Meta Ads & More

This recipe does the job if Google Analytics is your only destination, since it doesn't accept personal information or IP addresses anyway. But if you want a strong Event Match Quality (EMQ) score in Meta, or Enhanced Conversions in Google Ads, the data has to go server-side, together with the lead's name, email, click IDs and IP address. That is exactly what Converly does.

A Converly workflow sending HubSpot Forms conversions to ad platforms

Here's why Converly is the best way to send conversions to Google Ads, Meta Ads, ChatGPT Ads, Microsoft Ads, LinkedIn Ads, etc.

Easy to set up

Converly has a simple drag-and-drop builder for creating a Conversion Flow. Just pick a trigger (such as a HubSpot form being submitted), then pick your actions (such as sending a conversion to Google Ads). There's no custom code and nothing complicated to configure.

Sends Data Server-Side

Rather than relying on the visitor's browser the way Google Tag Manager does, Converly sends each conversion straight from its own servers to Google, Meta, LinkedIn and the rest, so ad blockers and privacy restrictions can't block it.

Sends More Data

It collects the lead's name, email and phone along with the Google and Facebook click IDs, IP address and user agent, and includes them with every conversion. That switches on Enhanced Conversions in Google Ads and gets you an Event Match Quality (EMQ) score of 9 or 10 in Meta.

Full Conversion Log & Email Notifications

See every lead that submitted one of your HubSpot forms, the data that was captured, what went to each platform, and whether each platform received it. Set up email alerts to tell you when something goes wrong, so your tracking never breaks without you knowing.

Supports Multiple Tools & Destinations

Picks up submissions from 100+ form, scheduling, and chat tools, and sends them to 20+ destinations, including Google Ads, Meta, LinkedIn, ChatGPT, Microsoft, and TikTok. So if you add a new tool or start advertising on a new platform, setting up conversion tracking is just a few clicks.

Provides Free Support

Our team brings 20 years of conversion tracking and analytics experience and is on hand whenever you need help. With Google Tag Manager, you're on your own.

Start Your Free Trial

Start a 14-day free trial and track your conversions the proper way. No code, no complicated configuration.

About the author

Aaron Beashel
Aaron Beashel

Aaron is the founder of Converly. With over 15 years of experience in digital marketing and SaaS, he's passionate about helping businesses track and optimise their ad conversions.

Start your free trial

Easily send conversions to your ad platforms and analytics tools.
No code required.