Block Duplicate Ecommerce Transactions: The Atomic Firestore Solution

Block duplicate transactions - Database only - SGTM Variable

I built the original version of this Server-Side GTM template back in 2021. It used a simple, effective method: storing the ecommerce transaction ID in a first-party cookie. If a user refreshed the “Thank You” page, the template saw the ID in the cookie and blocked the duplicate hit from reaching Google Analytics or the marketing tags.

For a long time, it did a decent job. But as the digital landscape shifted, the cookie-only approach hit a wall.

Duplicate transactions animated

The cracks appeared when the mobile payment app Vipps became popular in Norway. Payment apps can break the traditional browser session. The user may start in Chrome, complete the payment in the Vipps app, and then be returned to the device’s default browser, which may not be the browser where the purchase started. That browser has a completely different cookie jar. The deduplication cookie is missing, and the transaction fires twice.

Later on, I started working with a mobile app that suffered from a decent amount of duplicate ecommerce transactions. Google Analytics 4 has a native deduplication feature for web, but GA4 deduplication does not work for apps. Since my original template was purely cookie-based, it could not prevent app duplicates at all.

I turned to existing server-side database solutions using Stape Store and standard Firestore setups, but they did not help much. The reason was timing. The duplicated transactions were being sent within millisecond. The standard database logic of reading a document and then writing to it just was not fast enough to stop them. Both hits would check the database at the same time, see no duplicate, and write themselves.

The best practice is always to fix the root problem in the app or the website backend. However, in the real world, that is not always so easy. I started looking for a robust method to stop these split-second duplicate transactions using Server-side GTM, and that is when I discovered the Firestore atomic solution.

To solve these challenges, the Block Duplicate Transactions template for SGTM has been completely rewritten. It now features a flexible 3-mode architecture, advanced privacy controls, and Firestore atomic deduplication.

The Magic of Firestore Atomic Deduplication

When you have a race condition where an app or a payment webhook sends the exact same transaction to SGTM simultaneously, a read-then-write implementation can fail.

Firestore solves this using Atomic Operations.

Instead of asking if the document exists, the template fires a blind POST request instructing Firestore to create a document using the transaction_id as the document key.

  • The first hit arrives and creates the document. Firestore returns 200 OK.
  • A few millisecond later, the second hit arrives and tries to create the same document. Because the operation is atomic, Firestore instantly rejects it with a 409 Conflict.

The template catches this 409 error and returns true, allowing the configured trigger exception to block the tag. When Firestore successfully processes both requests against the same document, only one creation can succeed; competing requests are rejected as duplicates.

(Note: The template also supports Stape Store for Stape users who want a zero-configuration database. Stape Store is highly effective for standard cross-browser duplicates, but because it relies on a two-step GET and PUT process, it is a best effort solution and cannot protect against split-second race conditions).

Atomic Cuts Database Operations and Cost

Beyond accuracy, this atomic approach is incredibly cost-efficient. Cloud databases like Firestore bill based on the number of operations (reads and writes). A traditional deduplication setup requires two operations for every normal purchase: a read to check if the ID exists, followed by a write to save it.

By using a blind atomic POST, we skip the read entirely. We simply tell Firestore to create the document, and if it already exists, the 409 Conflict error from Firestore acts as the answer. For the vast majority of the traffic that consists of legitimate, first-time purchases, this literally cuts the database operations in half.

Tracking Blocked Duplicates by Firestore

A nice bonus of using Firestore is that you can actually see the atomic deduplication in action. Every time the database catches a duplicate, Firestore rejects the write attempt and returns a 409 Conflict status.

If you want to know exactly how many of these duplicates Firestore has actively blocked, you can open the Google Cloud Logs Explorer or Stape Outgoing Requests Log for your Server-Side GTM environment. By filtering your logs for outgoing HTTP requests to the Firestore API that result in a 409 error, you get a precise count of the duplicates that used atomic Firestore protection to be stopped. It is a great way to validate the setup and see the exact number duplicates you have prevented.

Managing Database Size with Time to Live (TTL)

If you use a cloud database, you do not want it to store old transaction IDs forever. Google Cloud Firestore has a native Time to Live feature that automatically deletes old documents. However, to use it, you must send the expiration time in a specific ISO 8601 string format. Since the Server-Side GTM API lacks native Date object support, doing this is normally quite tricky.

To solve this, the template includes a custom date math routine. It calculates the expiration date based on your expiration settings and formats it for Firestore. This uses the same concept and logic from a previous template I built to write TTL to Firestore using Server-Side GTM. You just check a box in the settings, and your database cleans up after itself automatically.

The Solution: A Hybrid Cloud Architecture

The updated template moves beyond the browser by introducing three configurable modes:

  • Cookie-Only: The classic, fast, and free method for simple setups.
  • Database-Only (Firestore or Stape Store): Completely server-side. Perfect when cookies do not exist.
  • Hybrid (Cookies and Database): The ultimate safeguard. It checks the local cookie first as a lightning-fast cache. If the cookie is missing, it falls back to the cloud database to verify the transaction. In hybrid mode, the database is only checked when absolutely necessary, keeping your cloud bill low.

Built for Privacy and Broken Implementation

The new version introduces 3 safety nets:

1. Smart Consent Routing

The template natively supports Google Consent Mode and manual CMP mapping. It offers full flexibility by allowing you to configure cookie and database storage independently. Depending on your technical setup, you can choose whether the database operation should evaluate the consent state or run as an independent server-side check alongside your cookies.

2. The Invisible Bodyguard (Ignored IDs)

Implementations break. It is an unavoidable fact of analytics. Sometimes a misfiring CMS will send serialized placeholder values like "null", "undefined", "NaN" or a blank string as the transaction ID.

If a template deduplicates "null", it will block every subsequent broken transaction, resulting in severe underreporting. Worse, it completely hides the problem. If those broken hits never reach your analytics platform, it’s harder to identify if the implementation needs to be fixed.

The new template hardcodes a safety net to permanently ignore these JavaScript errors, allowing them to pass through without poisoning your database. It also includes a custom Ignored IDs text field where you can enter specific placeholders (like 0 or test_order) that should bypass deduplication.

3. Event Namespacing & Gatekeeper

Duplicate tracking isn’t just a purchase problem, it could happen with refund events, too.

But deduplicating both creates a logic trap. If you only use the raw transaction ID (e.g., 12345) as your unique identifier, the template will see the refund event, recognize the ID from the original purchase, and instantly block the legitimate refund.

To solve this, the template automatically “namespaces” the deduplication key by combining the event name and the transaction ID (e.g., purchase|12345 and refund|12345). This means you get bulletproof deduplication for both event types, without them ever conflicting with each other.

The Gatekeeper

To give you complete control over this process, the template includes an Allowed Events gatekeeper. You define exactly which events should be protected (e.g. purchase, refund). If the template evaluates an event that isn’t on your list, it immediately steps aside and allows the tag to fire normally. This guarantees the deduplication logic only runs exactly when and where you intend it to.

How to Get Started

The updated template is live now. If you are already using the older version, the update is designed to remain backwards compatible.

  1. Add the Block Duplicate Transactions template from the GTM Template Gallery.
  2. Select your Deduplication Method (I recommend Cookies and Database).
  3. If using Firestore, add your Firebase Project ID and configure a TTL policy in Google Cloud to automatically clean up old transaction IDs.
  4. Add the Variable as an exception condition to your main purchase trigger(s).

Github Repository

You can add the template directly from the Google Tag Manager Template Gallery. If you want to review the source code or read the full technical documentation, you will find the complete repository on Github:

Be the first to comment on "Block Duplicate Ecommerce Transactions: The Atomic Firestore Solution"

Leave a comment

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.