HubSpot Form GTM Event Listener
Track HubSpot form submissions and pre-submission data in Google Tag Manager. Capture form GUID, field data, and fire GA4, Google Ads, and Meta Pixel on hubspot-form-success.
Event fired
hubspot-form-dataHubSpot Form
Overview
HubSpot forms are embedded via an iframe and communicate with the parent page using the browser's window.postMessage API. This listener intercepts those cross-origin messages and extracts two distinct events: a pre-submission event that captures all form field data, and a success event that confirms the submission completed.
Events fired: hubspot-form-data (pre-submit with fields), hubspot-form-success (confirmed submit)
Credit: 3WhiteHats (modified by DumbData)
Why Use This Listener
HubSpot forms render inside an iframe. GTM's standard Form Submit trigger cannot cross iframe boundaries, meaning without this listener, HubSpot form conversions are completely untrackable in GTM. This listener bridges the iframe gap using the PostMessage API that HubSpot uses internally.
| Without Listener | With Listener |
|---|---|
| HubSpot form iframes are invisible to GTM | Full visibility into form lifecycle |
| Zero conversion data | Submit confirmed + field data captured |
| No form identification | hs-form-guid uniquely identifies each form |
| Can't fire GA4 or pixels on HS forms | Any tag can fire on HS form success |
Common Use Cases
- CRM lead tracking, Match HubSpot form submissions to GA4 sessions for attribution
- Google Ads conversion tracking, Import HubSpot form fills as Google Ads conversions
- Multi-form attribution, Track which HubSpot form (demo, contact, newsletter) drives pipeline
- Enhanced conversions, Capture hashed email for Google's Enhanced Conversions
- LinkedIn Lead Gen correlation, Compare HubSpot form leads with LinkedIn campaign data
How It Works
HubSpot sends postMessage events from the embedded iframe to the parent window. The listener uses window.addEventListener('message', ...) to intercept these messages and filters for HubSpot-specific message types.
Two key message types:
hsFormCallbackwith typeonBeforeFormSubmit, fires before submission, contains field datahsFormCallbackwith typeonFormSubmitted, fires after successful submission
sequenceDiagram
participant U as User
participant HS as HubSpot iframe
participant PW as Parent Window
participant DL as dataLayer
participant GTM as GTM
U->>HS: Submits form
HS->>PW: postMessage(onBeforeFormSubmit)
PW->>DL: push hubspot-form-data
HS->>PW: postMessage(onFormSubmitted)
PW->>DL: push hubspot-form-success
DL->>GTM: Trigger firesGTM Setup Guide
Step 1: Create the Custom HTML Tag
- GTM → Tags → New → Custom HTML
- Name:
cHTML – HubSpot Form Listener - Paste the installation code below
- Trigger: All Pages (Pageview)
Step 2: Create Two Custom Event Triggers
Trigger 1, Pre-Submit Data:
- Event name:
hubspot-form-data - Use this to capture field data before submission
Trigger 2, Conversion Trigger:
- Event name:
hubspot-form-success - Attach GA4, Google Ads, Meta pixel to this trigger
Step 3: Create DataLayer Variables
| Variable Name | DL Key | Purpose |
|---|---|---|
| DLV – HS Form GUID | hs-form-guid | Unique identifier of the HubSpot form |
| DLV – HS Form Data | hs-formData | Array of submitted form fields (from pre-submit event) |
Installation
<!-- GTM Custom HTML Tag: HubSpot Form Listener -->
<!-- Trigger: All Pages (Pageview) -->
<script>
window.addEventListener('message', function(event) {
if (event.data.type === 'hsFormCallback') {
if (event.data.eventName === 'onBeforeFormSubmit') {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'hubspot-form-data',
'hs-form-guid': event.data.id,
'hs-formData': event.data.data
});
}
if (event.data.eventName === 'onFormSubmitted') {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'hubspot-form-success',
'hs-form-guid': event.data.id
});
}
}
});
</script>Data Layer Output
Pre-Submit Event (Field Data)
{
"event": "hubspot-form-data",
"hs-form-guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"hs-formData": [
{ "name": "firstname", "value": "Jane" },
{ "name": "email", "value": "jane@company.com" },
{ "name": "company", "value": "Acme Corp" }
]
}Success Event (Conversion)
{
"event": "hubspot-form-success",
"hs-form-guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Trigger Configuration
Primary Conversion Trigger
Trigger Type: Custom Event
Event Name: hubspot-form-success
Per-Form Trigger (Specific Form GUID)
Trigger Type: Custom Event
Event Name: hubspot-form-success
Condition: DLV – HS Form GUID | equals | a1b2c3d4-e5f6-7890-abcd-ef1234567890
Variables to Capture
| Variable Name | DL Key | Type |
|---|---|---|
| DLV – HS Form GUID | hs-form-guid | Data Layer Variable |
| DLV – HS Form Data | hs-formData | Data Layer Variable |
To extract the email from hs-formData, use a Custom JavaScript Variable:
function() {
var data = {{DLV - HS Form Data}};
if (!data) return '';
for (var i = 0; i < data.length; i++) {
if (data[i].name === 'email') return data[i].value;
}
return '';
}GA4 Mapping Recommendations
| GA4 Event | Parameter | GTM Variable |
|---|---|---|
generate_lead | form_id | DLV – HS Form GUID |
generate_lead | method | "hubspot" |
generate_lead | user_email | Custom JS (extract from formData) |
Debugging
Common Issues
| Problem | Cause | Fix |
|---|---|---|
| No events in dataLayer | HubSpot form embedded via API (not standard embed) | Check HubSpot embed method |
| Fires on page load | postMessage from HubSpot on init | Add if (event.origin.includes('hubspot')) check |
hs-formData empty | Using success event (not pre-submit) | Use hubspot-form-data event for field data |
| Double fires | Two listeners | Remove duplicate GTM containers |
Origin Filtering (Recommended for Security)
window.addEventListener('message', function(event) {
// Only process messages from HubSpot domains
if (!event.origin.includes('hs-scripts.com') &&
!event.origin.includes('hubspot.com') &&
!event.origin.includes('hsforms.net')) return;
if (event.data.type === 'hsFormCallback') {
// ... rest of listener
}
});Best Practices
- Always use
hubspot-form-successfor conversion tracking, neverhubspot-form-data(which fires before the submission completes server-side) - Create a GTM Lookup Table mapping form GUIDs to human-readable names for GA4 reporting
- Use the GUID, not position, HubSpot form positions change; the GUID is stable
- Consent gate the field data push, Only push PII fields (
email,name) if the user has consented to marketing cookies - Test with HubSpot's own tracking disabled, Avoid double-counting HubSpot's native analytics + your GTM tracking
Related Listeners
FAQ
Q: My HubSpot form is embedded via the HubSpot CMS, not WordPress. Does this still work? A: Yes, the PostMessage API is the same regardless of CMS.
Q: Does this capture the HubSpot-generated hs_context field?
A: The hs-formData array includes all form fields. hs_context is a hidden field with session/browser metadata.
Q: Can I track multi-step HubSpot forms?
A: onBeforeFormSubmit fires on each step submission. Use it to track step completions; use onFormSubmitted for the final conversion.
Q: What's the difference between hs-form-guid and the form portal ID?
A: The guid is unique per form. The portal ID is your HubSpot account ID. Use the guid for per-form segmentation.