How to Fix SAP Commerce Cloud Events Not Firing | OpsBlu Docs

How to Fix SAP Commerce Cloud Events Not Firing

Fix GA4, GTM, and pixel events not firing on SAP Commerce Cloud — Spartacus SSR/CSR context issues, SmartEdit conflicts, and CMS component debugging

General Guide: See Global Events Not Firing Guide for universal concepts and fixes.

SAP Commerce-Specific Causes

1. SSR vs CSR Context Issues

Code running in wrong rendering context:

// Problem: Window not available during SSR
constructor() {
  window.dataLayer.push({}); // Error on server!
}

2. Observable Subscription Issues

Events lost due to unsubscribed observables:

// Problem: Subscription completes before event fires
this.eventService.get('CartAddEntrySuccessEvent')
  .pipe(take(1)) // Only captures first event!
  .subscribe(event => this.track(event));

3. Spartacus Event Service Timing

Event service not initialized before subscription:

// Problem: Service not ready
ngOnInit() {
  this.eventService.get('PageEvent').subscribe(); // May miss early events
}

4. Accelerator JSP Include Order

Tag libraries loaded after tracking code:

<!-- Problem: dataLayer used before GTM loads -->
<script>
    dataLayer.push({'event': 'pageView'}); // dataLayer undefined!
</script>
<%@ taglib prefix="analytics" tagdir="/WEB-INF/tags/analytics" %>
<analytics:gtm containerId="${gtmId}"/>

SAP Commerce-Specific Fixes

Fix 1: Check Platform Before Browser APIs

Always verify platform context:

import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { PLATFORM_ID, Inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class TrackingService {
  constructor(
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  track(event: any): void {
    // Only execute in browser
    if (isPlatformBrowser(this.platformId)) {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push(event);
    }
  }

  // For server-side tracking
  trackServerSide(event: any): void {
    if (isPlatformServer(this.platformId)) {
      // Use Measurement Protocol or server-side logging
      this.sendToMeasurementProtocol(event);
    }
  }
}

Fix 2: Properly Manage Subscriptions

Use proper subscription management:

import { Subject, takeUntil } from 'rxjs';

@Component({...})
export class TrackingComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();

  constructor(private eventService: EventService) {}

  ngOnInit(): void {
    // Subscribe with takeUntil for cleanup
    this.eventService.get('CartAddEntrySuccessEvent')
      .pipe(takeUntil(this.destroy$))
      .subscribe(event => this.trackAddToCart(event));

    this.eventService.get('OrderPlacedEvent')
      .pipe(takeUntil(this.destroy$))
      .subscribe(event => this.trackPurchase(event));
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Fix 3: Initialize Tracking Early

Use APP_INITIALIZER for early setup:

// tracking.module.ts
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { TrackingService } from './tracking.service';

export function initializeTracking(trackingService: TrackingService) {
  return () => {
    trackingService.initialize();
    return Promise.resolve();
  };
}

@NgModule({
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializeTracking,
      deps: [TrackingService],
      multi: true
    }
  ]
})
export class TrackingModule {}
// tracking.service.ts
@Injectable({ providedIn: 'root' })
export class TrackingService {
  private initialized = false;

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    private eventService: EventService
  ) {}

  initialize(): void {
    if (this.initialized || !isPlatformBrowser(this.platformId)) {
      return;
    }

    // Initialize dataLayer
    window.dataLayer = window.dataLayer || [];

    // Set up event listeners
    this.setupEventListeners();

    this.initialized = true;
  }

  private setupEventListeners(): void {
    // All event subscriptions here
  }
}

Fix 4: Fix Accelerator Include Order

Correct tag library order in JSP:

<%-- 1. First: Initialize dataLayer --%>
<script>
    window.dataLayer = window.dataLayer || [];
</script>

<%-- 2. Then: Load GTM --%>
<%@ taglib prefix="analytics" tagdir="/WEB-INF/tags/analytics" %>
<analytics:gtm containerId="${gtmId}"/>

<%-- 3. Finally: Push page data --%>
<analytics:pageData pageType="${pageType}"/>

Fix 5: Handle Checkout Events

Spartacus checkout requires special handling:

@Injectable({ providedIn: 'root' })
export class CheckoutTrackingService {
  constructor(
    private eventService: EventService,
    private trackingService: TrackingService
  ) {
    this.setupCheckoutTracking();
  }

  private setupCheckoutTracking(): void {
    // Begin checkout
    this.eventService.get('CheckoutDeliveryAddressSetEvent')
      .subscribe(() => this.trackBeginCheckout());

    // Add shipping info
    this.eventService.get('CheckoutDeliveryModeSetEvent')
      .subscribe(event => this.trackAddShippingInfo(event));

    // Add payment info
    this.eventService.get('CheckoutPaymentDetailsSetEvent')
      .subscribe(event => this.trackAddPaymentInfo(event));

    // Purchase complete
    this.eventService.get('OrderPlacedEvent')
      .subscribe(event => this.trackPurchase(event));
  }

  private trackBeginCheckout(): void {
    this.trackingService.track({
      event: 'begin_checkout'
    });
  }

  private trackAddShippingInfo(event: any): void {
    this.trackingService.track({
      event: 'add_shipping_info',
      shipping_tier: event.deliveryMode?.code
    });
  }

  private trackAddPaymentInfo(event: any): void {
    this.trackingService.track({
      event: 'add_payment_info',
      payment_type: event.paymentDetails?.cardType?.name
    });
  }

  private trackPurchase(event: any): void {
    this.trackingService.track({
      event: 'purchase',
      transaction_id: event.order.code,
      value: event.order.totalPrice?.value,
      currency: event.order.totalPrice?.currencyIso
    });
  }
}

Debugging Checklist

Spartacus

// Debug utility
@Injectable({ providedIn: 'root' })
export class TrackingDebugService {
  constructor(@Inject(PLATFORM_ID) private platformId: Object) {}

  debug(): void {
    if (!isPlatformBrowser(this.platformId)) {
      console.log('Running on server - tracking disabled');
      return;
    }

    console.log('GTM loaded:', typeof google_tag_manager !== 'undefined');
    console.log('dataLayer:', window.dataLayer);
    console.log('dataLayer length:', window.dataLayer?.length);
  }
}

Accelerator

<%-- Debug mode --%>
<c:if test="${debugMode}">
    <script>
        console.log('GTM Container:', '${gtmContainerId}');
        console.log('dataLayer:', JSON.stringify(dataLayer, null, 2));
    </script>
</c:if>

Common Error Patterns

Symptom Cause Solution
No events in SSR Browser API in server context Use isPlatformBrowser
Missing cart events Subscription timing Use APP_INITIALIZER
Duplicate events Multiple subscriptions Manage with takeUntil
Events on some pages Inconsistent module import Import TrackingModule globally

Next Steps