Rajdip Khavad Logo
Rajdip Khavad
Back to all articles
Engineering

Preventing Duplicate Purchase Events in GA4

Rajdip Khavad•2026-09-01•8 min read

# Preventing Duplicate Purchase Events in GA4

Purchase tracking is one of the most sensitive parts of ecommerce analytics.

A hotel booking confirmation page can be refreshed, revisited or loaded again through browser navigation. If every confirmation-page load sends a new GA4 purchase event, one booking can appear as multiple transactions.

This article explains how to make purchase tracking idempotent in an Angular SPA.

1. Why Duplicate Purchases Happen

A simplified flow is:

📐 Architecture Diagram
Payment
  ↓
Booking completed
  ↓
Confirmation route
  ↓
purchase event

But users can also do:

📐 Architecture Diagram
Confirmation
  ↓
Refresh
  ↓
Confirmation
  ↓
purchase event again

Or:

📐 Architecture Diagram
Confirmation
  ↓
Navigate away
  ↓
Return to confirmation
  ↓
purchase event again

The UI lifecycle does not provide a reliable transaction boundary.

2. Use the Booking ID as the Transaction Identity

The booking ID provides a stable identity:

text
Booking ID = transaction identity

The analytics layer can use it to determine whether the purchase has already been sent during the browser session.

3. Session-Based Deduplication

A simplified implementation:

ts
const key = `purchase_tracked_${bookingId}`;

if (sessionStorage.getItem(key)) {
  return;
}

trackPurchase(bookingId);

sessionStorage.setItem(key, 'true');

The same principle can be applied to other events that require session-level deduplication.

4. Clear Ecommerce State Before Purchase

Before sending the purchase payload:

ts
window.dataLayer.push({
  ecommerce: null
});

Then push the new transaction:

ts
window.dataLayer.push({
  event: 'purchase',
  ecommerce: {
    transaction_id: bookingId,
    items
  }
});

This reduces the risk of stale ecommerce information.

5. Idempotency Is an Application Concern

The analytics provider cannot always know whether the application intentionally sent the same transaction again.

The application knows the booking identity.

Therefore:

📐 Architecture Diagram
Booking identity
       ↓
Analytics deduplication

belongs naturally in the application analytics service.

6. Why Not Use Component State?

Component state can disappear when the component is destroyed.

For example:

📐 Architecture Diagram
ConfirmationComponent
      ↓
destroyed
      ↓
created again

Any in-memory boolean such as:

ts
purchaseSent = true;

may be lost.

Browser session storage survives component recreation during the session.

7. Test the Failure Cases

A purchase implementation should be tested with:

  • normal booking completion
  • confirmation refresh
  • browser back/forward navigation
  • repeated confirmation access
  • repeated component initialization
  • multiple bookings in one session
  • Expected behaviour:

    text
    Booking A → purchase
    Booking A → skipped
    Booking B → purchase

    8. Do Not Hide Legitimate Purchases

    The deduplication key must be the transaction identity, not something broad such as:

    text
    purchase_tracked = true

    That would incorrectly block a second legitimate booking.

    Use:

    text
    purchase_tracked_<bookingId>

    instead.

    9. Broader Lesson

    Purchase events should be treated differently from ordinary interaction events.

    Viewing a hotel twice can be valid behaviour.

    Recording the same completed booking twice is usually a data-quality problem.

    10. Conclusion

    Reliable ecommerce analytics requires idempotent conversion tracking.

    Using a stable booking identifier, session-level deduplication and explicit ecommerce-state clearing provides a practical safeguard for Angular booking applications.

    Related Content

  • Proven Engineering

    Need enterprise architecture consulting?

    Explore our real-world case studies detailing production migrations, performance scaling, and high-throughput gateways.

    View Case Studies