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.