Duplicate Transactions in Google Analytics – The Check and the Fix

By far the most common issue I’ve come across with ecommerce sites; duplicate transactions can inflate revenue and ecommerce metrics, altering your attribution reports and making you question your data integrity.

When talking about where to put the ecommerce tracking code, Google suggests the following for Universal Analytics:

… If successful, the server redirects the user to a “Thank You” or receipt page with transaction details and a receipt of the purchase. You can use the analytics.js library to send the ecommerce data from the “Thank You” page to Google Analytics.”

The missing step here is to ensure that either A) the user cannot access the page more than once or B) you have logic in place to make sure the transaction is only sent once. The biggest issues I’ve seen are when this receipt page is automatically emailed to the customer, with the ability for them to return as frequently as they please, each time sending a duplicate transaction.

Many people incorrectly assume this is something that is handled through Google Analytics processing, or that if it does occur, it is a bug. In reality, it is simply an implementation issue and one that is often overlooked.

Within a session, Google Analytics will filter out duplicate transactions provided they have the same information. But if a visitor comes back later that day, or two weeks later, and another transaction is sent, then these will show up in your reports.

Check Your Data

How do we check if this issue exists in your ecommerce data? There’s a fairly simple Custom Report you can create to check for this. I’ve created a template which, if you have the appropriate permissions, you can attempt to import via this link or you search the Solutions Gallery for “Duplicate Transactions.” If you cannot import the Custom Report for some reason, simply create the report yourself with this setup (screenshot).

Adjust your date range to at least a month. If you have Transaction IDs that have multiple transactions, then you’re either A) sending in duplicate transactions or B) reusing transaction IDs, both of which should be corrected.

When Do Duplicate Transactions Occur?

The following scenarios are the most likely culprits for sending in the duplicate information:

Returning to the page via emailed link or bookmark

Refreshing the page

Navigating to a different page, and returning via back button

Page restoring from a closed browser session or on a smartphone

As you implement your solution, try checking each of these scenarios to make sure you’re completely covered!

Server-Side Is Better

There are several schools of thought around fixing duplicate transactions. My background is more on client-side implementations, specifically with Google Tag Manager. However, in general, if you have the resources and time to spend, I would recommend handling this issue server-side.

Without going into specifics, I would add in some sort of server-side logic to ensure that the ecommerce analytics code is only delivered once to the page. This could be using a database to record and check to see if the ecommerce info has already been sent.

It could also be some sort of server-side variable that is similarly checked. Another option I’ve seen is to redirect the user away from the receipt page after the ecommerce info has been sent to Google Analytics, then preventing the user from returning to that page.

Sometimes a page refresh doesn’t require fully reloading the page from the server, however, so make sure to test all of the above scenarios.

A Two-Pronged Approach

Not all of us have access to the server though, and sometimes we just need a solution. My tactic for dealing with duplicate transactions uses two different methods to attempt to determine if the transaction has already been sent.

A browser cookie records the transaction ID

A timestamp on the transaction serves as a backup

Cookies by themselves can filter out most duplicate transactions, but can be less than 100% effective due to privacy settings and user preferences. Someone can clear their cookies, browse in incognito mode, or pull up the same receipt on two different devices.

For that purpose, I also use a timestamp to help determine how old the transaction is. This timestamp should come from the page immediately before the receipt page, so very little time should pass. We can set this to be 15 or 30 minutes to be safe, just in case there’s some kind of validation check or third party system before they hit the receipt.

Here is the general user flow that we’ll follow. We’ll check to see if a cookie with this transaction ID exists. If it does, then we know it’s a repeat transaction, and we won’t send the ecommerce information to Google Analytics.

If there’s no cookie, we’ll check the timestamp. If there’s no timestamp, then we know it’s days or weeks old, from before the date we put our new process went into place, so we’ll label this as missing.

If there is a timestamp, how old is it? If it’s more than 30 minutes old, then we’ll assume it’s an old transaction and we’ll label this as expired.

Lastly, if there’s no cookie and it’s been less than 30 minutes, we’ll call this a new transaction. We’ll set a new cookie on this computer and then proceed with the checkout as normal.

Stopping Duplicate Transactions via Google Tag Manager

The instructions I’m including use Google Tag Manager, but feel free to adapt for your site using straight JavaScript. Going into this, I’ll assume you have Google Tag Manager already installed set up on your site, that you have a receipt page, and that the data layer is properly formatted for ecommerce.

To get the full functionality of this solution, you will need access to update the site or have a developer you can call to help you get the timestamp into place.

MACRO – Data Layer Variable – “transactionId”

This macro will simply return the transaction ID from the correctly formatted data layer on the receipt page.

MACRO – Data Layer Variable – “timeStamp”

This will be something we need to add to the site itself. We need to get the timestamp of the transaction, either from some server-side code, or by passing this value through the submit form. Either way, this piece does require you to update the site itself.

You can add a hidden field to your form and after the submit order button is pressed, use JavaScript to populate that field with new Date().getTime(). Then print this value onto the data layer on the receipt page. This needs to be calculated before the page loads and repeated onto the data layer, because this is supposed to be the time that the order was submitted, not the time that page was loaded.

Then we can use a simple macro to pull out this value.

RULE – “Receipt Page – Transaction Present”

This rule checks to see if we’re on the receipt page and if there is a transaction present.

TAG – Custom HTML – “Duplicate Transaction Checking”

Here is where the magic happens. This Custom HTML will take care of all of the work, checking for cookies, setting cookies, and checking the timestamp. The result is then pushed to the data layer with a custom event.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

<script type="text/javascript">

functioncheckCookies(){

varcookievalue="test";

varcname="";

cname="TID_{{transactionId}}=";

varca=document.cookie.split(';');

//Checks for existing Cookie

for(vari=0;i<ca.length;i++){

varck=ca[i].trim().toString();

if(ck.indexOf(cname)==0){

cookievalue=ck.substring(cname.length).toString();

break;

};

};

// Cookie is found, so repeat transaction

if(cookievalue>0){

dataLayer.push({'transactionType':'repeat'});

dataLayer.push({'event':'transactionChecked'});

}else{

//Check time as a backup

varvalidateDate={{timeStamp}};

varcurrentTime=newDate().getTime();

if(validateDate>0){

varminutes=Math.round((currentTime-validateDate)/1000/60)

//Set expiration time for new cookie

vard=newDate();

d.setTime(d.getTime()+(365*24*60*60*1000));

varexpires="expires="+d.toGMTString();

if(minutes<30){

//less than 30 minutes, so good transaction!

document.cookie="TID_{{transactionId}}="+validateDate+"; "+expires;

dataLayer.push({'transactionType':'new'});

dataLayer.push({'event':'transactionChecked'});

}else{

//older than 30 minutes, so expired transaction

document.cookie="TID_{{transactionId}}="+validateDate+"; "+expires;

dataLayer.push({'transactionType':'expired'});

dataLayer.push({'event':'transactionChecked'});

};

}else{

//no timestamp found, so must be old

document.cookie="TID_{{transactionId}}="+currentTime+"; "+expires;

dataLayer.push({'transactionType':'missing'});

dataLayer.push({'event':'transactionChecked'});

};

};

};

checkCookies()

</script>

MACRO – Data Layer Variable – “transactionType”

Now that the Tag has checked if the transaction is a duplicate, we’ll use this macro to pull out the result.

RULE – “New Transactions Only”

We’ll create a rule that uses the event and transaction type that gets pushed from the Duplicate Transaction Tag.

TAG – Google Analytics – “GA Ecommerce Transaction”

Finally, put it all together with a Google Analytics transaction Tag, with the Firing Rule set to “New Transactions Only.”

Parting Thoughts

That’s all there is to it! It’s a little complicated, but when you break it down step by step, it should make sense logically. I would recommend setting up a test property to send these ecommerce transactions to until you’re sure that this is working properly, then, make the switch at a time when there are few people using the site.

Questions/comments? Did you have duplicate transactions on your site?

Receive Blog UpdatesGet notified when blog posts like this one are published!

Jon Meck is our Technical Marketing Manager, promoting our services and trainings to the world. He has a jack-of-all-trades background, working for companies large and small in social media, website design and maintenance, and analytics. He is an Excel enthusiast, he loves efficiency, and he is strong proponent of the “Work Smarter, Not Harder” mantra. Jon is also the author of two number puzzle books.

Michael N.

Very great article and very interesting.

I am however facing a different issue with transactions GA report:

Number of transactions per transaction ID = 1 (no duplicate) – I even used your own report to double check (same as one I use)

Reported revenue for many transaction ID’s is very high – let’s say 1,125.00€ but if I click on the transaction ID to get the details, it reports 375 € (which correspond to the listed items , quantity & total).

While transactions for that ID = 1, its revenue = 3x the actual order value.

Any clue what could be the issue? It happens for many transactions and clearly inflates to the total revenue.

Sounds like an issue with your tracking code. When you send in the transaction, you have to fill out the transactionTotal, as well as each product price/quantity. So you probably have one of two issues:

– The calculated transactionTotal that you’re passing in is wrong
– Certain products are not being included in the transaction that you’re sending to Google

Do some checking and let me know if that was the issue!
-Jon

Jim

Hi,

Regarding the fix, how would you go about removing the duplicate data from (classic)GA?

I hesitate to run transaction reversals (i.e. transaction code with negative product and cost data) as I assume that this would reduce revenue and conversion data for the date the reversal was run, rather than the original transaction date. Is that correct?

Many thanks in advance for your thoughts on this.

Thanks

Jim

flipperfeet

I am seeing duplicate transactions, but one is assigned the correct local currencyCode and its couplet is captured as “(not set)” Have you run into this scenario?

Lisa Crotty

Hi Jon – with the new version of Tag Manager how would this solution be affected? Is there an updated version of the post? Thanks! Really enjoyed your Tag Manager class.

Yes – would love to confirm how to do with with Enhanced eCommerce, because there’s no transaction tag there, instead transactions fire when the event equals “purchase”. Please and thank you!! 🙂

Michael Kim

Put a more detailed post above yours, but the short answer is yes. If you block the general analytics tag from firing, the enhanced ecommerce flag shouldn’t fire either.

Michael Kim

I’ve just implemented with enhanced ecommerce – I use Tealium, not GTM, but the general concept would still apply. I create new dataSource, “DuplicateOrderFlag”, and set that flag to 1 on the order confirmation page instances/hits prior to the tags firing (pre-loader). Then when the tags fire i have load/fire rule for the entire GAP tag that says only fire GAP when the DuplicateOrderFlag does not exist.
Works like a charm.
Not sure how to replicate in GTM.. but the blog post looks like it should work, since the enhanced ecommerce event gets packaged within the general analytics event call. So if you don’t fire the general event call, the ecommerce won’t fire as well. You’ll loose that pageview, but you probably don’t care about that:).

For Premier Pools, this solution should work for non-transactional events as well. If you have a separate tag that fires to send that form submission, then the code requires very little work. If the form submission is part of some other app that’s not controled by a Tag Management Solution like GTM or Tealium IQ, then you may need to work with the app developer to do something similar.
Or, if you’re doing the web development yourself and it’s just a form tag in the html, you can write some similar javascript to Jon’s in order to check for duplication before posting the form. Just like you’d have address verification or required field verification.

Thanks for the post Jon! Very helpful!

Sarah Mackenzie

I have another question about getting the timestamp on the page. How do we inject that into the Datalayer, and what should we call it? Is it as simple as just adding a line to the Datalayer, like this?

timeStamp: ‘value’

Thanks!

Oscar

I believe that the Variable setup he mentions on ‘ MACRO – Data Layer Variable – “timeStamp” ‘ has you covered for this.

Jaron Clinton

Has anyone had problems with Duplicate Transaction IDs via Mobile. More than 90% of our dupes come via mobile and I was wondering if anyone else has experienced this? Thank you

Ellen

on the reverse side, I think that i had a user place two orders right after each other, but only one of them is showing up in our ecommerce tracking. Is there something in GA that would prevent multiple transactions from tracking during a session?