To join a user’s data from Google Analytics to any other platform, the single important dimension you need is Client ID. There are hundreds of articles that explain but we’ll give a small refresher.
Integrating Google Analytics 4 with Hubspot - Using Hubspot Form Hidden Field
What is Google Analytics Client ID?
Client ID is a unique value that identifies (pseudo anonymously) a visitor visiting from one browser. That is to say that if the same visitor visits your website from a different browser, a new client ID gets assigned to this visitor. Further, if the visitor deletes the cookies and revisits the website, a new client ID is dispatched.
Visitor A
Chrome > Client ID = 123.231
Safari > Client ID = 345.543
Firefox > Client ID = 678.876
Never mind the length of the client IDs in the above examples. Thats just for showing it in a simple form. Normally a client ID is a long string of number like 1234567890.1234567890 (notice a dot between the two numbers!).
Visitor A deletes cookies from Chrome and revists from all three browsers again
Chrome > Client ID = 999.878
Safari > Client ID = 345.543
Firefox > Client ID = 678.876
The client ID stays the same as long as the visitor keeps coming back from the same browser and does not delete cookies before doing so.
Other visitors will have their own client Ids assigned in their browsers.
Using Client ID to join GA with Hubspot
We’ll see how you can successfully capture a client ID and store it in your CRM systems. We’ll take Hubspot as an example but fundamentally this is method can be used for any other systems with slight adjustments.
The idea is to send the user’s client ID when they submit a form on your website, thus passing the value into your CRM system.
What’s the benefit of storing GA4 Client ID in Hubspot or any other system?
The benefit of storing client IDs in your system is two-fold:
- It helps you join Google Analytics 4 dataset with your CRM
- Not only that, it’ll also enable you to pass back events (like tracking email opens, or other offline events) from your CRM into your Google Analytics 4 account, thus, essentially creating a two-way street. Needless to say the event you pass back into GA4 gets attached to the same user data in GA4.
The process is simpler than it sounds.
Step 1: Add a hidden field to your Hubspot Form
This is a no-brainer step but if you’d like to store the client ID you need a Hubspot property to do that. In this case, we’ll use a hidden field in our forms and label it as “ga_client_id”
Step 2: Retrieve and update hidden field with the Client ID
How you retrieve client ID depends largely on the method you used to deploy your GA4 tracking code. It could be that you are either using gtag tracking code directly in your pages’ headers OR you are using Google Tag Manager. We’ll discuss both methods:
Choose a scenario
We have covered four scenarios here and you can jump to whatever is most relevant for your use case.
Scenario 1: Hubspot Form Embed Code & Gtag Tacking Code
- You have access to Hubspot form embed code
- You use Gtag tracking code on the website
Scenario 2: Using Hubspot Form Embed Code & Google Tag Manager
- You have access to Hubspot form embed code
- You use Google Tag Manager on your website
Scenario 3: Using only Google Tag Manager
- You do not have access to Hubspot form embed code or your page code
- You use Google Tag Manager on your website and have access to it
Scenario 4 (Rare): You can make changes to page code but not to the Hubspot Form code
- You have access to your CRM and can make changes to the code
- You cannot make changes to Hubspot Form Code
Scenario 1: Hubspot Form Embed Code & Gtag Tacking Code
- You have access to Hubspot form embed code
- You use Gtag tracking code on the website
First retrieve client ID with GTAG tracking code:
Retrieving client ID with Gtag tracking code is extremely simple since GA4 get command allows you to get the values easily
gtag('get', 'G-XXXXXXXXXXXX', 'client_id', function(client_id) { console.log("Retrieved Client ID: ", client_id); } });
You’ll have to of course replace G-XXXXXXXXXXXX with your own GA4 Measurement ID.
Update Hubspot form hidden field with retrieved Client ID
Next, we’ll use this function in our Hubspot embed code. Modify your hubspot code to the following:
<script charset="utf-8" type="text/javascript" src="//js-eu1.hsforms.net/forms/embed/v2.js"></script> <script> // Load the HubSpot form independently document.addEventListener("DOMContentLoaded", function() { hbspt.forms.create({ region: "eu1", portalId: "1111111", formId: "1111x1x1-1x11-1x11-1111-111x11111x11", onFormReady: function(form) { var hiddenField = document.querySelector('input[name="ga_client_id"]'); // Attempt to get the client ID when GA4 is available gtag('get', 'G-QVDGX7TRX8', 'client_id', function(client_id) { console.log("Fresh Retrieved Client ID: ", client_id); if (hiddenField) { hiddenField.value = client_id; } }); } }); }); </script>
Replace 1111111 with your portal ID
And 1111x1x1-1×11-1×11-1111-111x11111x11 witn your form ID
You’ll notice that we used the Hubspot onFormReady hook to
- getting the client ID from our GA tracker using the function described above
- set the value of the hidden field (ga_client_id) added in the previous step to the retrieved client ID
You might want to test it a few times before you take this live.
If your tests goes through, apply this change to all your forms.
Note:
If a user rejects permissions for GA tracking, the form will still load because it loads independently of whether the client is retrieved or not for a smooth user experience. However, if a user has Javascript disabled, then we’re out of luck on both counts!
Scenario 2: Hubspot Form Embed Code & Google Tag Manager
- You have access to Hubspot form embed code
- You use Google Tag Manager on your website
Retrieve Client ID from GA cookie and updating Hubspot Form Embed code (no changes needed in Google Tag Manager)
This method can essentially work with Gtag tracking code too. However unlike how we described previously, where we “get” the client ID from the ga tracker, here we retrieve the cookie value from the “ga cookie”.
Side note
Retrieving value from the cookie may not be 100% reliable if your function runs before the cookie is set, so you might have to queue the functions accordingly.
Retrieve the Client ID value from the ga cookie.
We’ll use this function.
function getGA4ClientId() { const cookieValue = document.cookie.split('; ').find(row => row.startsWith('_ga=')); return cookieValue ? cookieValue.split('=')[1].split('.').slice(-2).join(".") : 'not_set'; }
Next we use this function in our Hubspot form embed code as this:
<script charset="utf-8" type="text/javascript" src="//js-eu1.hsforms.net/forms/embed/v2.js"></script> <script> // Function to get GA4 Client ID function getGA4ClientId() { const cookieValue = document.cookie.split('; ').find(row => row.startsWith('_ga=')); return cookieValue ? cookieValue.split('=')[1].split('.').slice(-2).join(".") : 'not_set'; } hbspt.forms.create({ region: "eu1", portalId: "1111111", formId: "1111x1x1-1x11-1x11-1111-111x11111x11", onFormReady: function(form) { // This function is called when the form is fully rendered var clientId = getGA4ClientId(); // Set the value of the hidden field (assuming 'form_transaction_id' is the correct name) var hiddenField = document.querySelector('input[name="ga_client_id"]'); console.log(clientId); if (hiddenField) { hiddenField.value = clientId; } } }); </script>
Replace 1111111 with your portal ID
And 1111x1x1-1×11-1×11-1111-111x11111x11 witn your form ID
As you’ll see, in this function the fall back value is ‘not set’. This will also be a way for you to figure out the success of this implementation. A percentage of ‘not set’ values from the total contacts will give you the success rate of this method. Moreover, it also provides you an insight on how many users consent to GA4 tracking.
Scenario 3: Using only Google Tag Manager
- You do not have access to Hubspot Code
- You use Google Tag Manager on your website
- You have access to Google Tag Manager
We do the same thing as above here, just using GTM.
Retrieving GA Client ID from GTM 1st Party Cookie Variable
1. Create a new 1st party Cookie Variable. You can call it ‘ga Cookie’.
2. Create a second variable to extract the Client ID from this cookie
Create a new custom javascript variable, call it Client ID, and add the following code to it
function() { return {{ga cookie}}.substring(6) }
Go to GTM preview mode and test if you see the Client ID value in your variables list on a simple page view.
Update Hubspot Form Hidden Field via GTM
Next you’ll take this client ID and update the value of your GA Client ID hidden field in the Hubspot form.
The biggest problem when setting a hidden field in Hubspot form through GTM is the timing. Sometimes the GTM tag will load first and will not update the hidden field. Other times, it may do just as you’d like. To counter this, instead of adding a simple JS that updates the hidden field (like we did in our Hubspot embed code), the approach here would be to “observe” if the form has first loaded and only then update the hidden field.
Create a custom HTML tag and set the firing trigger to be the last of in the trigger sequences of GTM like “Window Loaded”. As for other conditions, you could either simply fire it on all pages or add specific condiutions so the tag fires only on pages where a form is available
Add this Javascript to it
<script> // Target the body or specific container where the form will be loaded var targetNode = document.body; // Create an observer instance var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { var inputField = document.querySelector('input[name="ga_client_id"]'); if (inputField) { inputField.value = {{client ID}}; console.log("Client ID set:", {{client ID}}); observer.disconnect(); // Stop observing once we've set the value } }); }); // Configuration of the observer: var config = { childList: true, subtree: true }; // Start observing observer.observe(targetNode, config); </script>
This script will actively monitor changes in the body (or another specified container), checking if the hidden field becomes available and setting its value as soon as it does. This approach is particularly useful for dynamically loaded content.
To know more about mutation observers check out the documentation – https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
CAVEAT
If the user deletes their cookies, a new client ID will get generated. So the same user may have 2 different client IDs stored on two different forms. The latest client ID will overwrite the previous one. You will be able to see the difference if you’ve deployed a data warehouse. However, for sending offline events back into GA4, this won’t matter as the event will get associated with the latest client ID of the user.
Scenario 4 (Rare): You can make changes to page code but not to the Hubspot Form code. What to do?
- You have access to your CRM and can make changes to the code
- You cannot make changes to Hubspot Form Code
- Works for both GTag tracking code and Google Tag Manager
It is possible that you have access to the page code but not to the Hubspot Form code – perhaps you are using a Hubspot extension in your CRM that directly embeds the form into the page. In such cases, you will combine the above methods. We’ll assume that you still have an access level that allows you to add Javascripts to your page. Without that, you’re just out of luck.
So you add the first Javascript that extracts the GA4 Client ID.
Then you add the JS we used in our custom HTML tag (descried above) but a small, yet significant change. Since this won’t be used in GTM, the GTM variable we refer to in this script won’t be available. Here is how the script would look like:
<script> function getGA4ClientId() { const cookieValue = document.cookie.split('; ').find(row => row.startsWith('_ga=')); return cookieValue ? cookieValue.split('=')[1].split('.').slice(-2).join(".") : 'not_set'; } var clientId = getGA4ClientId(); // Target the body or specific container where the form will be loaded var targetNode = document.body; // Create an observer instance var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { var inputField = document.querySelector('input[name="ga_client_id"]'); if (inputField) { inputField.value = clientId console.log("Client ID set:", clientId); observer.disconnect(); // Stop observing once we've set the value } }); }); // Configuration of the observer: var config = { childList: true, subtree: true }; // Start observing observer.observe(targetNode, config); </script>
Closing note: Custom Dimension in GA4 and High Cardinality
So far, what we did is – pass client IDs to Hubspot. But in order to create a connection between the two datasets, we need the client ID in GA4 too – which will then become the “join” key. If you have the join key missing in one dataset, there won’t be a join 😉
By default, GA4 does not store client ID. Its available in Big Query though. So unless you enable Big Query for your Google Analytics account – you’ll have to set up client IDs storage into a custom dimension.
A word of caution here – since client IDs are unique to every visitor, this custom dimension is quite prone to “High Cardinality” (i.e. dimensions with more than 500 unique values in one day.). If you have more than 500 visitors per day, you will most likely run into High Cardinality issues. The problem with high cardinality is when you look at GA reports, it will sample your data or aggregate most of it into an “Other” category (something quite frustrating).
Note that you need client IDs stored only for form submissions because users who submit the forms are the ones you will be able to connect with Hubspot – not all of them. Therefore, you could just send the client ID value to your custom dimensions on only form submit events. Once you have the client ID in – you should be able to create segments or audiences in GA4 to fetch the rest of the user behavior.
Still, for those seeking a seamless and robust integration, enabling BigQuery is highly recommended.
And there you have it—various methods for capturing and sending client IDs to HubSpot contact properties, regardless of whether you have access to the HubSpot embed code.
1 Comment