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.


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:
(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.
How to Use the HubSpot Forms GTM Recipe
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.

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.

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.

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.

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 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.
