Finding and fixing Duplicate Transactions (orders) in GA4 (Google Analytics 4)

What are duplicate transactions in Google Analytics 4 (GA4)?

Two or more transactions with the same transaction ID are called duplicate transactions. 

A transaction ID is a unique identifier that distinguishes one transaction (aka purchase order) from another.

You can use the order confirmation number as the transaction ID.

Why duplicate transactions in GA4 are bad?

Duplicate transactions can lead to inaccurate ecommerce metrics like inflated revenue, incorrect ecommerce conversion rate, inflated average order value and incorrect data in purchase and checkout journey reports.

This happens because each duplicate transaction is counted as a separate purchase.

Do you want expert help in setting up/fixing GA4 and GTM?

If you are not sure whether your GA4 property is setup correctly or you want expert help migrating to GA4 then contact us. We can fix your website tracking issues.

How to find duplicate transactions in GA4 (Google Analytics 4)

Follow the steps below to find duplicate transactions in GA4:

Step-1: Login to your GA4 property.

Step-2: Click on ‘Reports’:

Click on ‘Reports

Step-3: Navigate to the ‘Transactions’ report under ‘Monetization’:

Navigate to the ‘Transactions report

Step-3: Change the data range of the report to the last 31 days.

Step-4: Look at the ‘Ecommerce Purchases’ column of the report. They should all be 1.

Look at the ‘Ecommerce Purchases column of the report

If you find any value greater than 1, you have got duplicate transactions issue.

Step-5: Once you have discovered duplicate transactions, download this report, forward it to your developer and ask them to fix the duplicate transactions.

Step-6: Check the ‘Transactions’ report at least once a week to make sure that no new duplicate orders have been recorded in your GA4 property.

Causes of duplicate transactions in GA4

GA4 automatically de-duplicates transactions with the same transaction ID from the same user. 

However, if the same transaction ID is used for different users, such transactions are not automatically de-duplicated by GA4.

The following are the most common causes of duplicate transactions:

  1. Duplicate Data Layer events.
  2. Misconfiguration of GTM triggers.
  3. Double tagging.
  4. Unusual users’ actions.
  5. Incorrect server-side tagging configuration.

#1 Duplicate Data Layer events.

Duplicate dataLayer events can be a major contributor to duplicate transactions in GA4. 

When the same e-commerce event (like ‘purchase’) is pushed to the dataLayer multiple times, it can trigger multiple event firings and ultimately lead to duplicate transaction records.

For example, if a confirmation page is coded to push the transaction event to the dataLayer on each page load, and a user refreshes the page, the same transaction will be recorded multiple times in GA4

#2 Misconfiguration of GTM triggers.

Misconfiguration of triggers in Google Tag Manager can be a significant source of duplicate transactions. 

If triggers are not set up correctly, they can fire under unintended circumstances, leading to multiple event submissions and inaccurate data.

For example, if a click trigger is meant to fire only when the ‘Place order’ button is clicked but is configured too broadly, it might fire on other clicks on the same page, leading to duplicate transactions.

Similarly, if a ‘page view’ trigger is used to fire a transaction event but is not configured to recognise when a page view should NOT lead to an event submission (e.g., when a user revisits the confirmation page), it can cause duplicate transactions.

If multiple triggers are configured to fire the same tag and their conditions overlap, it can result in duplicate event submissions to GA4.

#3 Double tagging.

Double tagging is a common cause of duplicate transactions in GA4. 

When the same event, like a “purchase,” is fired multiple times through different tracking methods (GTM, gtag.js, measurement protocol, plugin, GA4 UI, etc.), it can result in duplicate event submissions and inaccurate data.

#4 Unusual users’ actions.

Unusual user actions can also lead to duplicate transactions.

For example:

#1 A user refreshes the page while the transaction is still being processed, leading to another transaction recording upon successful completion.

#2 The user used the browser’s back button to navigate back to the checkout page and then completed the transaction again, resulting in a duplicate entry.

#3 The user repeatedly clicks the purchase button because of a poor internet connection or bad habit.

#5 Incorrect server-side tagging configuration.

Incorrect server-side tagging configuration can lead to duplicate transactions in GA4 when transactions are mistakenly sent more than once.

This can be caused by issues like misconfigured triggers, incorrect handling of API responses, or failure to account for idempotency in the tagging setup.

For example, suppose the server-side logic does not properly handle response statuses or lacks idempotency checks.

In that case, it might resend transaction data to GA4, believing the first attempt failed when it actually succeeded.

How to fix Duplicate transactions (Orders) in GA4

  1. Follow the best practices for creating transaction IDs.
  2. Implement logic in your website’s code to check if an event with the same transaction ID has already been pushed to the dataLayer before pushing it again.
  3. Ensure that the code pushing ecommerce events to the dataLayer is only executed under the correct conditions.
  4. Fix misconfigurations of triggers in Google Tag Manager that lead to duplicate transactions.
  5. Fix double tagging issues.
  6. Track state changes to avoid duplicate transactions in GA4.
  7. Implement server-side verifications of transaction IDs to ensure they have not been processed previously.
  8. Use idempotency keys for API driven transactions.
  9. Use debouncing techniques to prevent duplicate transactions.
  10. Use throttling techniques to prevent duplicate transactions.

#1 Follow the best practices for creating transaction IDs.

#1 The transaction IDs in GA4 can be alphanumeric and, therefore, can include numbers, letters, and special characters. 

However, the only special character allowed in GA4 transaction IDs is the underscore _. 

Dashes (-) and spaces are not allowed in GA4 transaction IDs.

#2 A transaction ID must be unique within the last 31 days.

If a transaction ID is used again within this 31-day period, it will be considered a duplicate and may not be recorded or analysed correctly.

#3 The maximum length for a transaction ID in GA4 is 256 characters.

#4 Google recommends adding a unique transaction ID with each ‘purchase’ event.

#5 The transaction IDs should not include any personally identifiable information (PII) (i.e. the data that could be used to identify individual customers). A transaction ID without PII data ensures compliance with GDPR and safeguards customer privacy.

#6 When implementing ecommerce tracking in GA4, do not send an empty string (“”) as the transaction ID. If you send an empty string as the transaction ID, GA4 will deduplicate all such purchase events.

#7 Use the order IDs used by your shopping cart (like ‘Shopify) as your transaction IDs. That way, it would become easier to match your GA4 e-commerce data with your shopping cart data.

#2 Implement logic in your website’s code to check if an event with the same transaction ID has already been pushed to the dataLayer before pushing it again.

For example,

Step-1: Use a JavaScript object or array to track transaction IDs that have already been pushed to the dataLayer.

window.processedTransactions = window.processedTransactions || {};

Step-2: Before pushing a new transaction event to the dataLayer, check if the transaction ID has already been processed. If not, proceed to push the event and mark the transaction ID as processed.

function pushTransactionToDataLayer(transaction) {
    // transaction is an object containing transaction details, including the transactionId
    let transactionId = transaction.transactionId;

    // Check if the transaction ID has already been processed
    if (!window.processedTransactions[transactionId]) {
        // Mark the transaction ID as processed
        window.processedTransactions[transactionId] = true;

        // Push the transaction event to the dataLayer
        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push({
            'event': 'ecommerce_purchase',
            'transactionId': transactionId,
            'transactionTotal': transaction.total,
            'transactionProducts': transaction.products,
            // Add other transaction details as needed
        });

        console.log('Transaction pushed to dataLayer:', transactionId);
    } else {
        console.log('Duplicate transaction not pushed to dataLayer:', transactionId);
    }
}

When a user completes a purchase, you call pushTransactionToDataLayer with the transaction details. 

Suppose the user somehow triggers the purchase event again (e.g., by refreshing the page, clicking the purchase button multiple times due to interface lag, etc.).

In that case, the logic prevents a duplicate dataLayer.push() call for the same transaction ID.

#3 Ensure that the code pushing ecommerce events to the dataLayer is only executed under the correct conditions.

For example, only once when the confirmation page loads for the first time.

document.addEventListener('DOMContentLoaded', function() {
  // Unique identifier for the transaction, could be a transaction ID or similar
  var transactionId = 'TX12345678';

  // Check if the event for this transaction has already been pushed to the dataLayer
  if (!sessionStorage.getItem('transactionPushed_' + transactionId)) {
    // Push the e-commerce event to the dataLayer
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      'event': 'purchase',
      'transactionId': transactionId,
      'total': 100.00, // Example total amount
      // Include other transaction details as needed
    });

    // Mark this transaction as pushed to prevent duplicate pushes
    sessionStorage.setItem('transactionPushed_' + transactionId, 'true');
    
    console.log('E-commerce event pushed to dataLayer for transaction ID:', transactionId);
  } else {
    console.log('Transaction event already pushed for transaction ID:', transactionId);
  }
});

This approach ensures that even if the user refreshes the confirmation page, navigates away and returns, or otherwise reloads the page during their session, the e-commerce event for that specific transaction is only pushed to the dataLayer once.

This prevents duplicate transaction data from inflating your analytics reports.

#4 Fix misconfigurations of triggers in Google Tag Manager that lead to duplicate transactions. 

A common mistake is using triggers that are too broad, causing tags to fire more often than intended.

For example, if a transaction tag fires every time the confirmation page is refreshed, this can lead to duplicate transactions.

#5 Fix double tagging issues.

Fix double tagging issues by ensuring the same ecommerce event is not fired multiple times by different tracking methods (gtag.js, GTM, measurement protocol, third-party plugin, GA4 UI, etc.)

#6 Track state changes to avoid duplicate transactions in GA4.

For example,

If a user repeatedly performs an action (e.g., clicking the “purchase” button multiple times), tracking state changes helps you avoid counting each click as a separate purchase event, which could inflate your revenue metric and provide an inaccurate view of user behaviour.

Set an Initial State: When the page loads, initialize a state to track whether the transaction has been initiated.

let isTransactionInProgress = false;

Change State on Action: When the user clicks the “purchase” button, check the state before proceeding. If a transaction is not already in progress, proceed and change the state.

document.getElementById('purchase-button').addEventListener('click', function() {
    if (!isTransactionInProgress) {
        isTransactionInProgress = true; // Prevent further clicks from initiating transactions
        // Proceed with the transaction logic
        processPurchase(); // This function would handle the purchase logic and the `dataLayer.push()`
    }
});

Reset State Appropriately: After the transaction is complete or if it fails, reset the state to allow for future transactions. This could be set on the confirmation page or after an error handling routine.

function transactionComplete() {
    isTransactionInProgress = false; // Allow for new transactions
}

#7 Implement server-side verifications of transaction IDs to ensure they have not been processed previously.

This serves as a fail-safe against duplicate transactions that might slip through front-end defences.

For example, to implement server-side verification of transaction IDs in PHP, you would typically interact with a database to check if a given transaction ID has already been processed.

Below is a simplified PHP example demonstrating how you might perform this check using a MySQL database.

Prerequisites

  1. You should have a MySQL database with a table for storing transaction details.
  2. The PHP script needs to connect to your MySQL database.

This script checks if a transaction ID has already been processed before proceeding. If not, it attempts to insert the transaction into the database, marking it as processed. It assumes you’re sending a POST request with JSON data that includes a transactionId.

<?php
// Database connection variables
$host = 'localhost'; // or your database host
$dbname = 'your_database_name';
$username = 'your_username';
$password = 'your_password';

// Create a connection to the database
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);

// Assuming you're receiving JSON POST data
$input = json_decode(file_get_contents('php://input'), true);
$transactionId = $input['transactionId'];

// Function to check if the transaction has already been processed
function isTransactionProcessed($pdo, $transactionId) {
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM transactions WHERE transaction_id = :transactionId AND processed = TRUE");
    $stmt->execute(['transactionId' => $transactionId]);
    return $stmt->fetchColumn() > 0;
}

// Process the transaction if it hasn't been processed yet
if (!isTransactionProcessed($pdo, $transactionId)) {
    // Process the transaction here (e.g., mark as processed, update inventory, etc.)
    // For simplicity, we're just updating the `processed` flag in the database
    $stmt = $pdo->prepare("INSERT INTO transactions (transaction_id, processed) VALUES (:transactionId, TRUE) ON DUPLICATE KEY UPDATE processed = TRUE");
    $success = $stmt->execute(['transactionId' => $transactionId]);

    if ($success) {
        echo json_encode(['status' => 'success', 'message' => 'Transaction processed successfully.']);
    } else {
        echo json_encode(['status' => 'error', 'message' => 'Failed to process the transaction.']);
    }
} else {
    // Respond with an error if the transaction has already been processed
    echo json_encode(['status' => 'error', 'message' => 'Transaction has already been processed.']);
}
?>

The example code above demonstrates the core logic for preventing duplicate transaction processing using server-side PHP code.

#8 Use idempotency keys for API driven transactions.

For API-driven transactions, use idempotency keys to ensure that the purchase request is only processed once, even if it is received multiple times.

In API-driven transactions, an idempotency key is a unique identifier that prevents a server from processing the same request (such as making a payment) multiple times.

#9 Use debouncing techniques to prevent duplicate transactions.

Debouncing ensures that a function (such as an event tracking call) does not execute until a certain amount of time has passed since the last time the function was executed.

This technique can prevent rapid, repeated clicks from causing duplicate purchase events.

Here is an example of how you can implement a debouncing function to prevent duplicate purchase events from being tracked.

First, let’s define a generic debounce function. This function will delay the execution of the given function until after a specified wait time has elapsed since the last time it was invoked.

function debounce(func, wait) {
  let timeout;

  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

Now, let’s apply this debounce function to a hypothetical purchase button.

We assume that when the purchase button is clicked, it calls a function sendPurchaseEvent that would normally send the purchase event to GA4:

// Function to send the purchase event
function sendPurchaseEvent() {
  console.log("Purchase event sent");
  // Here, include the logic to send the event to your analytics platform, e.g., GA4
}

// Apply debounce to the sendPurchaseEvent function with a 60-second wait time
const debouncedSendPurchaseEvent = debounce(sendPurchaseEvent, 60000); // 60000 milliseconds = 60 seconds

// Assuming you have a purchase button with the ID 'purchase-button'
document.getElementById('purchase-button').addEventListener('click', debouncedSendPurchaseEvent);

In this setup, if a user clicks the “Purchase” button, the sendPurchaseEvent function will only be called after 60 seconds have passed without any further clicks.

If the user clicks multiple times within the 60-second interval, the timer resets, and the function call is delayed until the clicking stops. This effectively prevents rapid, repeated clicks from causing duplicate purchase events.

Note: To prevent user confusion and frustration during the debounce period, provide immediate visual feedback (e.g., disabling the button or showing a loader animation) to indicate that the user’s action has been registered and is being processed.

#10 Use throttling techniques to prevent duplicate transactions.

Implementing throttling techniques can also effectively prevent rapid, repeated actions (such as multiple clicks on a “Submit” or “Purchase” button) from causing duplication in your event tracking.

A throttling function limits the number of times a function can be executed in a certain amount of time.

Below is an example of a JavaScript function implementing throttling to prevent duplicate transaction events in GA4:

// Throttle function: Limits the rate at which a function can fire
function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function() {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  }
}

// Example transaction event function to be throttled
function sendTransactionEvent() {
  console.log("Transaction event sent to GA4");
  // Place your GA4 transaction event code here. For example:
  // gtag('event', 'purchase', { transaction_id: 'TX123', value: 100.00 });
}

// Wrap the transaction event function with the throttle function, limiting calls to once per 5 minutes (300000 milliseconds)
const throttledSendTransactionEvent = throttle(sendTransactionEvent, 300000);

// Attach the throttled function to the "Purchase" button click event
document.getElementById('purchase-button').addEventListener('click', throttledSendTransactionEvent);

The throttle function creates a throttled version of a specified function (sendTransactionEvent in this case) that can only be triggered once every specified limit (300,000 milliseconds or 5 minutes).

If invoked multiple times within the limit, it only executes after the limit period expires from the last invocation.

This approach effectively mitigates the risk of recording duplicate transactions due to user actions like multiple clicks, thereby maintaining the integrity and accuracy of your transaction data in GA4.

Note: If you maintain the exact same code on your staging website, the e-commerce data sent from the staging website should go to your GA4 test property rather than the live GA4 property.

Debouncing vs. Throttling to avoid duplicate purchases in GA4

Imagine an e-commerce website with a “Complete Purchase” button.

When clicked, it triggers a function to process the transaction and record the purchase event.

You want to avoid duplicate purchase events if the user clicks the button multiple times quickly, either by accident or because they are impatient while waiting for the confirmation page to load.

Use debouncing to wait until the user has finished making rapid clicks, processing the purchase only after they have stopped.

This is particularly useful for ensuring that a single intentional action is captured.

Example use case: Ensuring the purchase is only processed after the user stops clicking, avoiding unintended multiple transactions.

Use throttling to control the rate of function execution during continuous clicks, ensuring the purchase is processed immediately but not too frequently.

Example use case: Preventing multiple transactions during a short, continuous series of clicks.

Debouncing delays the function execution until there’s a pause in the event triggering the function.

Whereas, throttling controls the execution frequency to a fixed rate, ensuring the function executes at regular intervals.

In summary, choose debouncing when you need the function to run once after the events have paused or stopped and choose throttling when you need to guarantee that a function runs periodically throughout a series of events.

Data Discrepancy between GA4 and Shopping Cart Sales Data

GA4 and shopping cart sales data generally do not match. There can be many reasons for that.

But one of the main reason is shopping carts (like Shopify) handles sales data (cancelled orders, unfulfilled orders, test orders, promotions, and refunds) much better than GA4.

To learn more about the discrepancies between Google Analytics and shopping cart sales data, read this article: Why Google Analytics and Shopping Cart Sales data don’t match and how to fix it.

My best selling books on Digital Analytics and Conversion Optimization

Maths and Stats for Web Analytics and Conversion Optimization
This expert guide will teach you how to leverage the knowledge of maths and statistics in order to accurately interpret data and take actions, which can quickly improve the bottom-line of your online business.

Master the Essentials of Email Marketing Analytics
This book focuses solely on the ‘analytics’ that power your email marketing optimization program and will help you dramatically reduce your cost per acquisition and increase marketing ROI by tracking the performance of the various KPIs and metrics used for email marketing.

Attribution Modelling in Google Analytics and BeyondSECOND EDITION OUT NOW!
Attribution modelling is the process of determining the most effective marketing channels for investment. This book has been written to help you implement attribution modelling. It will teach you how to leverage the knowledge of attribution modelling in order to allocate marketing budget and understand buying behaviour.

Attribution Modelling in Google Ads and Facebook
This book has been written to help you implement attribution modelling in Google Ads (Google AdWords) and Facebook. It will teach you, how to leverage the knowledge of attribution modelling in order to understand the customer purchasing journey and determine the most effective marketing channels for investment.

About the Author

Himanshu Sharma

  • Founder, OptimizeSmart.com
  • Over 15 years of experience in digital analytics and marketing
  • Author of four best-selling books on digital analytics and conversion optimization
  • Nominated for Digital Analytics Association Awards for Excellence
  • Runs one of the most popular blogs in the world on digital analytics
  • Consultant to countless small and big businesses over the decade