TeamWhat We DoCase StudiesBlogToolsBook a consultation →
Tracking & GTM Guides

How to Track HubSpot Forms in GTM (V3 and V4 Embeds)

One GTM listener that pushes every HubSpot form submission, from legacy (v3) and new-editor (v4) embeds, into the dataLayer, plus the trigger, variables and GA4 tag to use it.

Published 4 min read
Diagram of a HubSpot form submission flowing into the Google Tag Manager dataLayer
Jump to
  1. What you'll build
  2. Why you need two listeners
  3. Step 1: Add the listener as a Custom HTML tag
  4. What the v4 block is doing
  5. Step 2: Create the Data Layer Variables
  6. Step 3: Create the custom event trigger
  7. Step 4: Send the lead to GA4
  8. Step 5: Test it in Preview mode
  9. Troubleshooting

HubSpot's embed code handles the form submission itself, so GTM's built-in Form Submission trigger often misses it. HubSpot does announce every successful submission in the browser, though. You just have to listen for it. One Custom HTML tag catches that announcement and pushes a form_submission event into the dataLayer. Everything else is standard GTM: a custom event trigger, two Data Layer Variables and a GA4 tag.

The listener below is adapted from Julius Fedorovicius's guide at Analytics Mania, Track HubSpot Forms with Google Tag Manager and Google Analytics 4. Credit for the approach goes to him.

What you'll build

GTM itemTypeWhat it does
cHTML - HubSpot form listenerCustom HTML tagListens for HubSpot submissions and pushes form_submission to the dataLayer
DLV - form_idData Layer VariableReads the HubSpot form GUID
DLV - conversion_idData Layer VariableReads HubSpot's ID for that single submission
CE - form_submissionCustom Event triggerFires when the listener pushes a submission
GA4 - generate_leadGA4 Event tagSends the lead to GA4 with the form ID attached

Why you need two listeners

HubSpot has two generations of forms, and they announce submissions differently:

  • Legacy forms (v3 embed) post a browser message with type: "hsFormCallback". The onFormSubmitted callback fires after HubSpot accepts the submission.
  • Forms built in HubSpot's updated forms editor (v4 embed) fire a global window event instead: hs-form-event:on-submission:success. HubSpot documents the legacy callbacks and the new global events separately, so don't count on the old listener catching new forms.

Most portals have both kinds live, so the listener handles both and pushes the same event shape either way. Your GTM setup never has to care which embed a page uses.

Step 1: Add the listener as a Custom HTML tag

In GTM, go to Tags → New → Custom HTML, name it cHTML - HubSpot form listener, and paste:

<script>
window.dataLayer = window.dataLayer || [];

//listener for older version (v3) of HS forms
window.addEventListener('message', function(event) {
  if (event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormSubmitted') {
    window.dataLayer.push({
      event: 'form_submission',
      form_id: event.data.id,
      conversion_id: event.data.data.conversionId,
      form_data: event.data.data.submissionValues,
    });
  }
});

//listener for v4 HS forms
window.addEventListener("hs-form-event:on-submission:success", function(event) {
  var formsApi = window.HubSpotFormsV4 || window.HubspotFormsV4;
  var hsform = formsApi && formsApi.getFormFromEvent(event);
  if (hsform) {
    hsform.getFormFieldValues().then(function(fieldValues) {
      var transformedData = fieldValues.reduce(function(obj, item) {
        var key = item.name.split('/')[1];
        if (key) {
          obj[key] = item.value;
        }
        return obj;
      }, {});

      window.dataLayer.push({
        event: "form_submission",
        form_id: hsform.getFormId(),
        conversion_id: hsform.getConversionId(),
        form_data: transformedData
      });
    });
  }
});
</script>

For the trigger, use Page View → Some Page Views limited to the pages that carry forms. That keeps the listener off pages that don't need it. If forms live all over the site, the built-in All Pages trigger is fine.

What the v4 block is doing

New-editor field names come back prefixed with an object type ID, for example 0-1/email. The reduce strips that prefix, so form_data ends up as a flat object like { email: "…", firstname: "…" }. HubSpot's docs note that getFormFieldValues() doesn't return file-upload fields, so don't expect them in form_data.

Step 2: Create the Data Layer Variables

Go to Variables → User-Defined Variables → New → Data Layer Variable and create:

Variable nameData Layer Variable NameVersion
DLV - form_idform_idVersion 2
DLV - conversion_idconversion_idVersion 2

form_id is the form's GUID, the same ID you see in the form's embed code (formId in a v3 embed, data-form-id in a v4 embed). That's the value you'll filter and report on.

Step 3: Create the custom event trigger

Go to Triggers → New → Custom Event and set:

  • Name: CE - form_submission
  • Event name: form_submission
  • This trigger fires on: All Custom Events

To track only one form, switch to Some Custom Events and add the condition DLV - form_id equals that form's GUID. Build one trigger per form you want to treat differently, for example a demo request vs. a newsletter signup.

Step 4: Send the lead to GA4

Create a Google Analytics: GA4 Event tag:

  • Event name: generate_lead, GA4's recommended event for lead forms
  • Event parameters: form_id = {{DLV - form_id}}
  • Trigger: CE - form_submission

In GA4, register form_id as an event-scoped custom dimension so you can break leads out by form. Then mark generate_lead as a key event in Admin → Events.

Step 5: Test it in Preview mode

  1. Click Preview in GTM and open a page with a HubSpot form.
  2. Submit a test entry.
  3. In Tag Assistant, confirm form_submission appears in the event list on the left.
  4. Open the Variables tab on that event and check that DLV - form_id holds the form's GUID.
  5. Confirm GA4 - generate_lead fired, then watch for the event in GA4 DebugView.

Test one legacy form and one v4 form if your portal has both. They take different paths through the listener.

Troubleshooting

  • No form_submission event at all: check that the Custom HTML tag fired on that page. If it did, check whether the form is inside a pop-up or a third-party iframe. The listener can only hear events on the page it runs on.
  • The event fires but form_data is empty on a v4 form: form_id and conversion_id are the values to build on. Treat form_data as a bonus and verify it per form.
  • Duplicate leads: make sure the listener tag isn't firing twice, for example on both All Pages and a page-specific trigger. Each fire adds another set of listeners.
Next step

Want help with Google Tag Manager Management?

Talk to the team behind this playbook. We'll look at your setup and tell you what we'd do next.

See Google Tag Manager Management →
Book →