How To Use GTM To Send GA4 Events To 2 Properties

There’s times when it’s useful to send GA4 events to two (or more) GA4 properties. Maybe you’ve got a GA4 “Rollup property” for multiple domains or a property to gather all errors in one place.

Update November 2023:
The new Google Variable “Google Tag: Configuration Settings” allows you to setup parameters in one place. You can then apply these parameters to the two GA4 setup tags using the one configuration tag. If you’ve got lots of parameters then this will be useful.

I was hoping for some ID flexibility, but not there yet.

GA4 Setup Tag With Configuration Variable

Update January 2024: I now use a JavaScript variable for the “Tag ID”. This allows me to dynamically change the ID depending on if it’s an internal or test user. I’ve got a special GA4 property to receive internal or test hits. This keeps my normal GA4 property cleaner, with less internal traffic contamination.

Here’s how the variable is configured:

You may read that using JavaScript variables is resource intensive. But in fact this one will only be run when it’s needed. However when you use Tag Assistant you’ll find it gets run VERY often. (you can add a console.log(“being run”); to check). Once you’ve published the GTM container the JS variable is only run when it’s needed – not so often.

Back to GA4 setup using a configuration variable…

Here’s how to do that using GTM. It’s easier than you might think.

The GA4 Setup Tag in GTM (Google Tag Manager – without configuration tag)

GA4 setup tag

The “Measurement ID” is what determines which GA4 property any events are sent to.

Putting two Measurement IDs in the setup (with a comma separating them) doesn’t work (it’s not that simple). GTM picks the first ID and uses that.

We could use two GA4 Setup Tags. One for each property. The second one is exactly the same as the first except for the Measurement ID.

The Second GA4 Property Setup Tag (without configuration tag)

GA4 second setup GTM tag

However an undocumented trick that works (for me) is to put multiple GA4 Measurement IDs in a JavaScript Array:

['G-AAAAAAAAA','G-BBBBBBBBB']

You can’t put this in the tag directly, but I do it as a Custom JavaScript Variable.

Now when you load a page there’s 2 page_view events sent. One to each property.

Here is where it gets simple. Lets add another event – in this case “test_event”. The trick is to “Manually Set ID” for the event. Using a GTM Custom JavaScript variable with the two Measurement IDs in a JavaScript Array.

The Array contains the Measurement IDs you want to send events to.

I have a special ID GTM variable with both Measurement IDs setup inside.

This is how we use the combined ID variable

GA4 test_event GTM tag

Now when we load a page the “test_event” is sent to both GA4 properties.

Two test events sent to two properties.

The images are from Chrome Dev Tools showing the two almost identical events sent to the two different GA4 properties.

This works for me in simple Google Analytics 4 setups in Tag Manager.

My previous version used comma separated GA4 Measurement IDs, however the GA cookies were compromised. Using the JavaScript Array method is much more reliable.

If you get any issues please contact me, and I’ll check.

Sending Offline Conversions Back to Google Ads Offline Conversions API

If you are spending money with Google Ads it’s essential to know what results you are getting. Otherwise you won’t know what’s successful and what’s not.

And If you can get the “final” conversion back to Google Ads you are in a much better position than if you can only send an intermediate conversion – like a lead or a click.

You can link Google Ads and GA4 (and you should) and send conversions to GA4 using Measurement Protocol. But the most direct and reliable method is to send the conversions straight to Google Ads using their API.

I’ve used Make.com to connect to the Google offline conversion API – it’s a bit fiddly to do yourself using a few POSTS/GETs etc. (especially oauth authorization).

With Make.com it’s very easy. You need to authorize access to the API and then fill in a few values.

I get the conversion details from a webhook (sent from n8n) and the notification is just for my entertainment. The only parameter that needs special attention in Make is the “Conversion Date Time”. You need to format it as Google wants – in make that’s:

{{formatDate(parseDate(1.json.trans_timestamp; "X"); "YYYY-MM-DD HH:mm:ssZ")}}

The parses the date into a UNIX timestamp and then formats that to what Google wants. eg “2023-08-02 19:32:45-05:00” you can see the formatting tokens explained at make: https://www.make.com/en/help/functions/tokens-for-date-time-formatting.

Make sure that this time is a “real” one. If you send two conversions with the same date time with the same click ID then Google doesn’t like it and rejects the conversion.

In order to match the conversion to a click to your site (or something similar with Apple IOS 14+) you’ll need to send a click ID back to Google along with the conversion details.

This ID is normally “gclid”, however there’s now also “wbraid” and “gbraid”. The latter associated with app conversions, which I don’t need in this example.

Getting “gclid”, “wbraid” or “gbraid”

For sending these offline conversions to Google you need to have saved and associated with your visitor these ID values.

If you don’t have these IDs saved then your option is to use “Enhanced Conversions” which uses a anonymized version of the personal information you collect in the conversion to attempt to match to Google’s (vast) set of data. But having an ID is better.

The IDs are available as URL parameters when a visitor comes from Google Ads to your website. Like:

 yoursite.com/landing-page/?gclid=xyz123abc987

Saving gclid and other URL Parameters in Google Analytics 4

If you are using Google Tag Manager (GTM) then you can easily setup a variable that gives you access to gclid (and any other URL parameters you want).

gclid using GTM variable

You can then add to the GA4 setup to send back to Google Analytics.

To be able to access and see this value in GA4 reports you’ll need to set up an “event scoped” Custom Dimension to match.

Note: Just an example for the Custom Dimension name and Parameter name. I would generally use CDgclid (for the custom dimension in GA4) and CPgclid (for the parameter defined in the GTM tag)

You’ll also be able to access this variable in BigQuery in the GA4 export. (after you link GA4 to BigQuery).

As an alternative I send all this type of information to BigQuery using Jitsu.

Another way to save gclid, etc. is to save them into a hidden field in forms – lead, checkout, etc. Most CRMs (HubSpot, Active Campaign, etc.) and Lead forms have the capability to add these sort of hidden fields.

You’ll need to grab the URL parameter using (most likely) GTM as above (or using some JavaScript code). Then, on the page where your form is, you can use a JavaScript Tag to copy the gclid value into the hidden field. When setup correctly this method can be very effective.

This way the click ID is directly associated with a lead or purchase, which is mainly the conversions we are dealing with.

You also need to be able to connect the conversion to the saved click IDs. This could be via a saved user_id in GA4 or by saving the IDs in the form as I mentioned above.

But when that conversion comes in you have to be able to get the IDs to send to Google.

In my case using an affiliate network I use a unique “subid” per click to the affiliate network. If a conversion occurs I can get this subid along with the conversion.

Then I’m able to query BigQuery using n8n to obtain any gclid, wbraid or gbraid that’s connected to that subid.

I feed to those to Make.com and it sends the offline conversion to Google.

So far it’s been working well.

How to Send Offline Conversions to GA4 Using Measurement Protocol (MP)

If you’ve got some offline or late conversions coming in after a visitor leaves your site then it’s useful to be able to feed them into Google Analytics. In this case GA4.

One of the ways you can do this is called “Measurement Protocol” – MP for short.

To use MP you’ll need to get or create the MP secret from your GA4 Admin area:
Admin >> Data Streams

Then click on the stream you want to send the conversions to. And you’ll see:

Measurement Protocol API Secret

Click on the “Measurement Protocol API secrets” bar and you can Create a secret or copy one if you’ve previously created a secret.

Create a Measurement Protocol API Secret for GA4

Once you have the secret store it in a safe place – that you’ll remember.

To send a conversion to GA4 you’ll need a few bits of information (mainly IDs) to tie the conversions to what Google knows about the converting visitor and the session when they were active:

  • client id – this is the GA4 id for the visitor – for example, like this 232912142.1691119463 – Google is able to match a conversion to a particular visitor who has been on your website.
  • session id – the actual session that you want to give credit for the conversion “1691126066” – Google uses this to match the conversion to a particular visitor “session”. Visitors might have multiple sessions on your site.
  • timestamp micros – this is a “microsecond timestamp”, which should be within the session. This is something like “1706828159565000” (which converts to Thu, 01 Feb 2024 22:56:23 GMT).
  • (and “non_personalized_ads“:false to keep retargeting working)

I’ll show you how to find these and how I store them later on.

Plus you’ll need the actual conversion information, just like you would for a normal on website conversion. I like to send the following (some are optional):

  • transaction id – unique id for the specific transaction (if you need to send a refund to GA4 this will have to match the purchase transaction id
  • currency – eg USD can specify currency at the item level (but avoid multi-currency if at all possible) I generally would put this parameter at the top level of parameters.
  • value – (items price * items quantity) + shipping + tax
  • shipping
  • tax
  • items (an array of item objects)

Measurement Protocol POST JSON

These all go into a JSON POST to “https://www.google-analytics.com/mp/collect?api_secret=SECRET&measurement_id=G-XXXXXXXXX”

like this:

{"client_id": 232912142.1691119463,
"non_personalized_ads":false,
"timestamp_micros":1691126435123456,
"events": [{
	"name": "purchase",
	"params": {
		"session_id":1691119463,
		"currency": "USD",
		"transaction_id": "ABC123",
		"value": 13.41,
		"shipping": 1.00,
		"tax": 0, 
		"items":[{
			"item_id": "XYZ987,
			"item_name":"MY Item",
			"quantity":1,
			"price":12.41
			}]
		}
	}]
}

Note: there’s a debug URL you can send your POSTs to if it’s not working too well at:

https://www.google-analytics.com/debug/mp/collect

Effectively this is a “validator” for your POST. It should give you a useful validation message or messages.

See:
https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events?client_type=gtag

I use n8n to send this POST to Google. It’s a nifty open source workflow tool I self host. You could use Zapier or make.com as well. Or code it up in a language like JavaScript/PHP/etc.

Getting the Client ID and Session ID

Both these IDs are stored as part of the Google Analytics first party cookies when a visitor is on your site:

The client ID is in “_ga” and the Session ID is in _ga_XXXXXXX (corresponding to the Measurement ID of you Google Analytics 4 property). You have to extract the part of the cookie you need. The last number for the Session ID and the last two large numbers (and the connecting “.”) for the Client ID.

You can also obtain these values using the gtag “get” api – see:
https://developers.google.com/tag-platform/gtagjs/reference#get

To get the GA4 client_id and session_id using gtag:

gtag('get', 'G-XXXXXXXXXX', 'client_id', function(client_id){
  console.log(client_id);/* do something here */
});

gtag('get', 'G-XXXXXXXXXX', 'session_id', function(session_id){
  console.log(session_id);/* do something here */
});

Note that the Client ID is stored in BiqQuery by the automatic GA4 to BigQuery export. The Session ID is stored as one of the event parameters, with key “ga_session_id”.

The session ID is just a second timestamp. And it’s not necessarily unique. If you have a reasonable number of visitors then there’s likely to be overlap on session IDs. For a unique id concatenate the client_id and the session_id.

I often do something like this in BigQuery SQL:

CONCAT(client_id, session_id) as unique_id -- this would be after extracting the session_id from the event parameter field

You can store the session ID as a GA4 event parameter then define it as a custom dimension in GA4. Then it will be available as an dimension in GA4 reports.

Getting The “timestamp_micros” Parameter Correct in Measurement Protocol

The timestamp micros is a microsecond UNIX timestamp that is during the session. If you don’t use this parameter in this way Google shows the conversion in GA4, but it doesn’t assign it to the correct source/channel.

I generally save all this information, including the timestamp value, just after GA4 has sent it’s first page_view event. You can get a JavaScript callback when the event has finished.

For gtag:

gtag('event','page_view',
    {'event_callback': function(){
      var gaId = readCookie('_ga');
      var gsId = readCookie('_ga_'+GA4ID);

      serverev('track', 'info', {'ga_id':gaId,'gs_id':gsId}, 'se');
    }
  })

That’s my “serverev info” event with all the required information (the timestamp is automatic). I’m sending this to BigQuery using Jitsu, but you can do something similar in other ways.

The other key is to be able to “reconnect” you conversion to the information you’ve saved.

You can directly save it in a CRM / Sales system as hidden fields so it’s available later.

In my case the conversion is via an affiliate network. I make sure I send a “subid” to the network with each click. If I get a conversion I can obtain the associated subid.

Then I use the subid to join the conversion to the information I’ve saved in BigQuery earlier.

And so when the conversion data becomes available, hours or days after the session I can use SQL on BigQuery and get the information to build the POST to Google Measurement Protocol.

n8n has a BiqQuery connector. This allows me to run the SQL and obtain the client_id, session_id and timestamp_micros values I saved earlier. Then I can just POST to MP using the JSON format I showed above.

Usability Testing

There seem to be lots of testing services out there…

from (now defunct) feedbackarmy.com writted in a custom language  to $1000/month all you can eat. (and probably even higher end.)

https://userinput.io

ask questions for feedback from users. ($39 for 10 minimum).
worth a go if you know what to ask. No free trial (but money back if it’s not useful).

https://99tests.com/

It’s bug reporting and testing not usability.

https://ferpection.com

4,500 Euros for complete report. Next.

https://usabilityhub.com

On the “Free Trial” you can purchase 100 credits for $100 (nothing less…).
It seems to show an image of your site/page for participants to click on, comment on, etc.

Next.

https://www.testbirds.com

They have “birdcoins”, “nest” subdomains, too cute for me. And also not the right type of service.

https://helio.app/

Nothing about pricing. Generally means it’s not so affordable… Sign up with no clue. No thanks.

https://userbob.com