How Do I Access The Individual Timestamp Of A GA4 Event?

All Google Analytics 4 events share the timestamp of their batch. With some customization, you can add individual timestamps to (almost) all events.

The question we’re going to be looking at today is inspired by our course, Query GA4 Data in Google BigQuery.

How do I access the individual timestamp of a GA4 event?

One of the features of Google Analytics 4’s client-side measurement is that the platform collects events in batches. This means that when an event happens, it isn’t immediately dispatched to Google Analytics 4.

Instead, the client-side library waits up to 5 seconds for other events to happen, at which point all the events that happened during this batch window are dispatched.

This is not that uncommon with telemetry tools. Batching is common especially on mobile devices, where constant network requests can quickly deplete the device battery, for example.

However, there’s a huge downside to this with Google Analytics 4. For some reason, individual events are not timestamped nor do they have a sequence indicator. All events share the timestamp of the batch request itself, which leads to the following, unbelievable conclusion: by default, it’s not possible to know WHEN a certain event happened, or what the sequence of events in any given batch was.

In this article, I’ll walk you through the simple steps of how to diagnose this issue as well as how to fix it by adding a custom timestamp to your events. There are some caveats, however. Hopefully, at some point Google will figure out this mistake and add timestamps to all events of any given batch.

Video walkthrough

If you prefer this walkthrough in video format, you can check out the video below.

Don’t forget to subscribe to the Simmer YouTube channel for more content like this.


Overview of the problem

To identify the problem, run the following query against your Google Analytics 4 data. You can choose any date you wish. This query simply pulls in user_pseudo_id and all events associated with each user, ordered by timestamp.


This is what the results might look like:

Users, events, and timestamps in BigQuery

If you scroll the results far enough, you should see multiple event_name rows that share the user_pseudo_id and that all have the same event_timestamp.

In these cases, these events were collected in a batch, and all events share the same timestamp.

In the screenshot above, I’ve circled these multi-event batches in red. As you can see, the second batch has FIVE events that all share the same timestamp. How on earth am I supposed to build sequence analyses when there are five events of which I don’t know the relative sequence or the exact time when they occurred?

In my opinion, this is a fairly big mistake from Google and one that should be fixed.

Luckily, there is something you can do to fix this proactively. You can add a custom event parameter to all your GA4 events so that they’ll have a timestamp associated with them.

Add the event parameter to your GA4 tags

Because the timestamp needs to be evaluated for all GA4 event tags separately, you shouldn’t add it to the GA4 Configuration tag / hit

At this point, if you’re still using the automatically collected Page View option in the Configuration tag, I recommend disabling it and sending the page_view as its own GA4 event. This gives you far more control over data collection.

In Google Tag Manager, you can create a Custom JavaScript variable that generates the timestamp with this:

function() {
  return new Date().getTime();

Even better, you can use a custom template to get the timestamp without having to use a Custom JavaScript variable. This template does the job nicely: Timestamp by luratic.

Once you’ve created the variable, you need to add it to all your GA4 event tags.

Add the custom timestamp to all GA4 event tags

If you are using gtag.js, the process is very similar, except you will just need to generate the timestamp with JavaScript.

gtag('event', 'custom_click', {
  custom_timestamp: new Date().getTime()

Once the custom parameter has been added to your events, you’re ready to start collecting that data for analysis.

Warning! I don’t recommend creating a custom dimension in GA4 for the timestamp, because this might lead to severe cardinality issues in your data. It’s best to leave the granular analysis to Google BigQuery.

Analyze the data in Google BigQuery

Once you’ve added the custom event parameter, you can modify your query in Google BigQuery to see the impact.

Here’s the query from the beginning of this article, modified to include the custom event parameter.

  TIMESTAMP_MICROS(event_timestamp) AS event_timestamp,
  TIMESTAMP_MILLIS((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'custom_timestamp')) AS custom_timestamp,

And here are the results.

Fixed BigQuery result with custom event timestamp included

If you look at the highlighted rows (4–8), you can see how the individual events in the batch are now associated with their own timestamps. You can now more clearly identify the sequence of events.

Note that in the case of the highlighted rows, we got “lucky” in that BigQuery actually displayed the events in the correct order (the custom timestamps are in ascending order even though we didn’t ORDER BY them). 

If you look at rows 11–13, you can see that the three events in that particular batch are not in the correct order. Based on the custom_timestamp parameter, page_view came first, then view_item, and finally fetch_user_data.

This is why it’s so important to collect the custom timestamp.

Caveat & Summary

One important caveat is apparent in the screenshot above. The custom parameter is not collected for events that GA4 generates independent of your event tags. session_start, first_visit, and user_engagement are such events.

While session_start and first_visit are generally easy to attribute to the page_view that’s in the same batch as them, user_engagement is a difficult nut to crack, because there’s no way to add custom parameters to that automatically generated event.

So there will be minor inconsistencies, but at least for all the events that matter, you now have a way of aligning them properly in your data warehouse.

In summary, this is one of those articles that I really hope will become redundant soon, because that would mean that Google has fixed this issue in the data collection. While batching is a good feature in general, it’s kind of inexcusable to ignore the granularity of event collection in 2023. It shouldn’t be too difficult to automatically add a time index to all events – it doesn’t have to be the full timestamp, just the offset from the batch time would do, for example.

13 Responses

  1. Hi.!
    I have restrictions in my organization about using javascript in my GTM.
    Is there a no-code solution in GTM like a pre-defined variable or something else that can return the timestamp?

  2. Hi Simo,

    I have defined Date().getTime() in the Event settings variable, and I’m still getting an issue where my own custom timestamp are the same (little better than event_timestamp)
    Do you think this should be defined in the individual tag level?

    1. Hi

      Yes, it needs to be in the individual tag level but you can use an event settings variable for it.

      You’ll continue seeing the same timestamp for session_start and first_visit events, but other events should have a unique timestamp.

        1. Yes, you can add it to the Google Tag too in a configuration settings variable, in case you’re collecting the page_view event with the Google Tag. But it needs to be added to every event tag as well – otherwise all events will inherit from the Google Tag the one and the same event timestamp that was set when the Google Tag first fired.

  3. Simo, can the order of events in a batch be arranged using engagement_time_msec or is Google working on a completely different solution?

    1. Hi!

      They can’t, as it’s not a sequential number.

      From what I’ve understood, Google is working on a solution where a new value that’s sent with each event contains the millisecond delta to the batch timestamp, so that can be used to order the events. I don’t have any info on when this will actually be available.

  4. I fail to understand how this issue could have slipped through the cracks … it is so essential to build user flows or establish the foundation for any kind of TS ML or causal inference (since causes can’t be in the future). Is the GA team not using GA themselves? Or maybe not with these use cases? No development partner mentioned this problem?
    We are working with an extensive form of time series here (the foundation of user analytics) and the time resolution is … event batch time?!

    1. Google doesn’t have development partners. They have enterprise partners and agencies, and many of them have voices their concerns over lack of granularity with the batch. I think the batching logic was developed before it was decided to release the raw data to BigQuery.

      In any case, as far as I know Google is working on a solution to this, so that individual events in a batch will have an “ordering ID” associated with them.

  5. Simo,
    You talk about disabling the Configuration Tag and sending the GA4 Page View event separately. Do you have documentation on how to do this?

    1. Hi

      You don’t disable the configuration tag. You disable the automatic page view collection through it, and you create a separate tag for the page view event to fire on whatever trigger makes most sense. I don’t have any documentation about it because that’s pretty much the gist 🙂 The idea is to take control of the tracking yourself rather than rely on automatic tracking mechanisms.

Thoughts? Comment Below 👇

Your email address will not be published. Required fields are marked *

More from the Simmer Blog

This blog post covers how to kick-start your career in technical marketing and data analytics.
This guide outlines the process of serving multiple main domains through a single server-side tagging container in Google Tag Manager.
How to migrate from one cloud service to another without changing the domain name mapped to the server-side tagging endpoint.
Hide picture