Skip to main content

Event System

The Glood SDK’s event system automatically transforms Shopify Analytics events into Glood pixel data and transmits them to configured endpoints with full privacy compliance.

Architecture Overview

The event system follows this flow:

Components

  1. Event Subscription - Automatically subscribes to Shopify Analytics events
  2. Event Distribution - Routes events to interested apps
  3. Data Transformation - Converts Shopify data to Glood pixel format
  4. Consent Checking - Verifies customer privacy permissions
  5. Pixel Queue - Manages immediate transmission with retry logic
  6. Error Handling - Comprehensive error handling and logging

Event Types

The SDK tracks these Shopify Analytics events:

Core Events

Event TypeDescriptionTriggered When
page_viewedPage navigationUser visits any page
product_viewedProduct page viewUser views product details
collection_viewedCollection page viewUser browses product collections
cart_viewedShopping cart viewUser opens shopping cart
search_submittedSearch queryUser performs search

Commerce Events

Event TypeDescriptionTriggered When
product_added_to_cartAdd to cartUser adds product to cart
product_removed_from_cartRemove from cartUser removes product from cart
custom_promotion_viewedPromotion viewUser sees promotional content

Event Transformation

The SDK transforms Shopify Analytics events into Glood pixel format using sophisticated data transformation:

Shopify → Glood Transformation

// Shopify Analytics Event
{
  eventType: 'product_viewed',
  products: [{
    id: 'gid://shopify/Product/123',
    title: 'Cool T-Shirt',
    price: '29.99',
    variantId: 'gid://shopify/ProductVariant/456',
    // ... other Shopify data
  }],
  shop: { currency: 'USD' },
  // ... other event data
}

// Transformed to Glood Pixel
{
  event: {
    id: 'evt_abc123',
    name: 'product_viewed',
    type: 'standard',
    timestamp: '2024-03-15T10:30:00.000Z',
    clientId: 'client_xyz789',
    context: {
      page: { url: '/products/cool-t-shirt', title: 'Cool T-Shirt' },
      userAgent: 'Mozilla/5.0...',
      screen: { width: 1920, height: 1080 }
    },
    data: {
      productVariant: {
        id: '456',
        title: 'Cool T-Shirt',
        price: { amount: 29.99, currencyCode: 'USD' },
        product: {
          id: '123',
          title: 'Cool T-Shirt',
          url: '/products/cool-t-shirt'
        }
      }
    }
  },
  rkUid: 'rk_user_id',
  data: {
    customer: null,
    cart: null,
    shop: {
      name: 'My Store',
      myshopifyDomain: 'my-store.myshopify.com',
      paymentSettings: { currencyCode: 'USD' }
    }
  },
  customerPrivacy: {
    analyticsProcessingAllowed: true,
    marketingAllowed: true,
    preferencesProcessingAllowed: true,
    saleOfDataAllowed: false
  },
  sessionId: 'session_def456',
  cartIdentifier: null
}

Event-Specific Transformations

Product Viewed

// Extracted product data
data: {
  productVariant: {
    id: '456',
    title: 'Cool T-Shirt - Blue',
    price: { amount: 29.99, currencyCode: 'USD' },
    product: {
      id: '123',
      title: 'Cool T-Shirt',
      vendor: 'Fashion Brand',
      url: '/products/cool-t-shirt',
      type: 'Apparel'
    },
    sku: 'TSH-BLUE-M',
    image: { src: 'https://example.com/image.jpg' }
  }
}

Search Submitted

// Search query and results
data: {
  searchResult: {
    query: 'blue shirt',
    productVariants: [
      {
        id: '456',
        title: 'Blue Cotton Shirt',
        price: { amount: 39.99, currencyCode: 'USD' },
        product: {
          id: '123',
          title: 'Cotton Shirt',
          url: '/products/cotton-shirt'
        }
      }
      // ... more search results
    ]
  }
}

Cart Viewed

// Complete cart contents
data: {
  cart: {
    id: 'cart_123',
    cost: {
      totalAmount: { amount: 89.97, currencyCode: 'USD' }
    },
    lines: [
      {
        cost: { totalAmount: { amount: 29.99, currencyCode: 'USD' } },
        merchandise: {
          id: '456',
          title: 'Cool T-Shirt - Blue',
          price: { amount: 29.99, currencyCode: 'USD' },
          product: {
            id: '123',
            title: 'Cool T-Shirt',
            url: '/products/cool-t-shirt'
          }
        },
        quantity: 1
      }
      // ... more cart lines
    ],
    totalQuantity: 3
  }
}

Pixel Transmission

Immediate Transmission

The SDK uses immediate pixel transmission with retry logic:
class PixelQueue {
  add(item: PixelEvent): void {
    // Send event immediately
    this.sendEventWithRetry(item.endpoint, item.data, 0);
  }

  private async sendEventWithRetry(
    endpoint: string,
    event: Record<string, any>,
    retryCount: number
  ): Promise<void> {
    try {
      const response = await fetch(endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(event),
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
    } catch (error) {
      // Retry with exponential backoff
      if (retryCount < this.retryDelays.length) {
        setTimeout(() => {
          this.sendEventWithRetry(endpoint, event, retryCount + 1);
        }, this.retryDelays[retryCount]);
      }
    }
  }
}

Retry Strategy

  • Retry Delays: [1000, 2000, 4000, 8000] milliseconds
  • Max Retries: 4 attempts total (initial + 3 retries)
  • Backoff: Exponential backoff strategy
  • Error Logging: Comprehensive error logging with debug details

Network Resilience

// Automatic retry on network failures
const retryDelays = [1000, 2000, 4000, 8000]; // 1s, 2s, 4s, 8s

// Error scenarios handled:
// - Network timeouts
// - HTTP 5xx server errors
// - HTTP 4xx client errors (logged but not retried)
// - JSON serialization errors
// - CORS issues
The SDK automatically checks customer consent before sending pixels:
function checkConsent(
  requiredConsents: ConsentType[],
  canTrack: any,
  analytics: any
): boolean {
  if (!requiredConsents || requiredConsents.length === 0) {
    return true; // No consent required
  }

  return requiredConsents.every(consentType => {
    switch (consentType) {
      case 'analytics':
        return analytics.customerPrivacy?.analyticsProcessingAllowed();
      case 'marketing':
        return analytics.customerPrivacy?.marketingAllowed();
      case 'preferences':
        return analytics.customerPrivacy?.preferencesProcessingAllowed();
      case 'sale_of_data':
        return analytics.customerPrivacy?.saleOfDataAllowed();
      default:
        return false;
    }
  });
}
AppDefault Consent RequirementsDescription
Recommendations['analytics', 'marketing']Analytics for tracking + marketing for personalization
Search['analytics']Only analytics for search improvement
Wishlist['analytics', 'preferences']Analytics + preferences for wishlist personalization

Privacy Scenarios

// Example consent scenarios
const consentScenarios = {
  noConsent: {
    analyticsProcessingAllowed: false,
    marketingAllowed: false,
    preferencesProcessingAllowed: false,
    saleOfDataAllowed: false,
  },
  analyticsOnly: {
    analyticsProcessingAllowed: true,
    marketingAllowed: false,
    preferencesProcessingAllowed: false,
    saleOfDataAllowed: false,
  },
  fullConsent: {
    analyticsProcessingAllowed: true,
    marketingAllowed: true,
    preferencesProcessingAllowed: true,
    saleOfDataAllowed: true,
  },
};

// Results for each app:
// Recommendations: noConsent ❌, analyticsOnly ❌, fullConsent ✅
// Search:          noConsent ❌, analyticsOnly ✅, fullConsent ✅
// Wishlist:        noConsent ❌, analyticsOnly ❌, fullConsent ✅

Session & Identity Management

Client ID Generation

class CookieStorage {
  static getOrGenerateClientId(): string {
    const existingId = this.getCookie('glood_client_id');
    if (existingId) return existingId;

    const newId = `client_${this.generateRandomId()}`;
    this.setCookie('glood_client_id', newId, 365); // 1 year
    return newId;
  }
}

Session Management

// Session ID (browser session)
static getOrGenerateSessionId(): string {
  const existingId = sessionStorage.getItem('glood_session_id');
  if (existingId) return existingId;

  const newId = `session_${this.generateRandomId()}`;
  sessionStorage.setItem('glood_session_id', newId);
  return newId;
}

// RK UID (analytics user ID)
static getOrGenerateRkUid(analytics: any): string {
  // Try to get from analytics first
  const analyticsId = analytics?.customerId || analytics?.customData?.userId;
  if (analyticsId) return analyticsId;

  // Fallback to cookie-based ID
  return this.getOrGenerateClientId();
}

Browser Context

const browserContext = {
  getContext(): BrowserContext {
    return {
      page: {
        url: window.location.href,
        title: document.title,
        referrer: document.referrer,
      },
      userAgent: navigator.userAgent,
      screen: {
        width: screen.width,
        height: screen.height,
      },
      language: navigator.language,
      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    };
  }
};

Debug Logging

Debug Mode Events

Enable debug mode to see detailed event flow:
const glood = createGlood({
  apiKey: process.env.GLOOD_API_KEY!,
  myShopifyDomain: 'your-store.myshopify.com',
  debug: true, // Enable debug logging
});

Debug Log Examples

[Glood Debug] Setting up event subscriptions for apps: ['recommendations', 'search']
[Glood Debug] App recommendations subscribes to events: ['page_viewed', 'product_viewed', ...]
[Glood Debug] All unique event types: ['page_viewed', 'product_viewed', 'search_submitted']
[Glood Debug] Setting up centralized subscription for product_viewed
[Glood Debug] Received product_viewed event, distributing to interested apps
[Glood Debug] Apps interested in product_viewed: ['recommendations']
[Glood Debug] Processing product_viewed event for recommendations: { products: [...] }
[Glood Debug] Consent granted for recommendations, processing event
[Glood Debug] recommendations app sending pixel: { event: { ... } }
[Glood Debug] Sending pixel event immediately: { endpoint: 'https://events.glood.ai', data: {...} }
[Glood Debug] Sending event to https://events.glood.ai (attempt 1): { event: {...} }
[Glood Debug] Successfully sent event to https://events.glood.ai

Error Debug Logs

[Glood Debug] Consent not granted for recommendations, skipping event
[Glood Debug] Retrying event in 2000ms (attempt 2)
[Glood Debug] Failed event: { event: {...} }
[Glood] Failed to send pixel event after all retries: NetworkError

Performance Considerations

Event Deduplication

Events are subscribed to only once per event type:
// Single subscription per event type, distributed to multiple apps
allEventTypes.forEach((eventType: EventType) => {
  subscribe(eventType, (eventData: any) => {
    // Find all interested apps
    const interestedApps = enabledAppsWithPixel.filter(app =>
      app.subscribedEvents.includes(eventType)
    );

    // Distribute to all interested apps
    interestedApps.forEach(app => {
      app.handleEvent(eventType, eventData, client);
    });
  });
});

Immediate Transmission

No batching delays - events are sent immediately:
// No queuing delays
pixelQueue.add({
  endpoint: this.pixel.endpoint,
  data: transformedPixelData,
}); // Sends immediately

Memory Management

  • No accumulation - Events are not stored in memory
  • Immediate processing - Events are transformed and sent immediately
  • Cleanup - Event handlers are properly cleaned up on unmount

Error Handling

Network Errors

// Automatic retry with exponential backoff
catch (error) {
  if (retryCount < this.retryDelays.length) {
    const delay = this.retryDelays[retryCount];
    setTimeout(() => {
      this.sendEventWithRetry(endpoint, event, retryCount + 1);
    }, delay);
  } else {
    console.error('[Glood] Failed to send pixel event after all retries:', error);
  }
}
// Graceful handling when consent is denied
if (!checkConsent(app.pixel.consent, canTrack, analytics)) {
  if (debug) {
    console.log(`[Glood Debug] Consent not granted for ${app.name}, skipping event`);
  }
  return; // Skip event processing
}

Transformation Errors

// Safe transformation with fallbacks
try {
  const pixelData = this.transformEventData(eventType, eventData, client);
  this.sendPixel(pixelData, client);
} catch (error) {
  console.error(`[Glood] Error transforming ${eventType} event:`, error);
  if (client.debug) {
    console.error('[Glood Debug] Original event data:', eventData);
  }
}

Custom Event Handling

Accessing Raw Events

import { useGloodAnalytics } from '@glood/hydrogen';

function CustomAnalytics() {
  const glood = useGloodAnalytics();

  useEffect(() => {
    if (!glood) return;

    const recommendationsApp = glood.getApp('recommendations');
    if (!recommendationsApp) return;

    // Custom event handling
    const originalHandler = recommendationsApp.handleEvent.bind(recommendationsApp);
    recommendationsApp.handleEvent = (eventType, eventData, client) => {
      // Custom processing
      console.log('Custom event processing:', { eventType, eventData });

      // Call original handler
      originalHandler(eventType, eventData, client);
    };
  }, [glood]);

  return null;
}

Custom Data Enrichment

// Extend event data before transformation
function enrichEventData(eventData: any): any {
  return {
    ...eventData,
    customField: 'custom value',
    timestamp: new Date().toISOString(),
    sessionData: getSessionData(),
  };
}

Best Practices

1. Debug Mode in Development

const glood = createGlood({
  apiKey: process.env.GLOOD_API_KEY!,
  myShopifyDomain: process.env.SHOPIFY_DOMAIN!,
  debug: process.env.NODE_ENV === 'development',
});

2. Monitor Network Errors

// Add global error handler for pixel transmission issues
window.addEventListener('unhandledrejection', (event) => {
  if (event.reason?.message?.includes('Glood')) {
    console.error('Glood pixel transmission error:', event.reason);
    // Report to error tracking service
  }
});
// Ensure appropriate consent for each app
const glood = createGlood({
  apps: {
    recommendations: {
      pixel: {
        consent: ['analytics', 'marketing'], // Appropriate for personalization
      },
    },
    search: {
      pixel: {
        consent: ['analytics'], // Minimal for search improvement
      },
    },
  },
});

4. Test Event Flow

// Verify events are flowing correctly
const glood = createGlood({
  debug: true, // Enable to see event flow
});

// Check browser console for:
// - Event subscriptions setup
// - Event reception and distribution
// - Consent checking results
// - Pixel transmission success/failure

See Also