Documentation Index
Fetch the complete documentation index at: https://docs.glood.ai/llms.txt
Use this file to discover all available pages before exploring further.
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
- Event Subscription - Automatically subscribes to Shopify Analytics events
- Event Distribution - Routes events to interested apps
- Data Transformation - Converts Shopify data to Glood pixel format
- Consent Checking - Verifies customer privacy permissions
- Pixel Queue - Manages immediate transmission with retry logic
- Error Handling - Comprehensive error handling and logging
Event Types
The SDK tracks these Shopify Analytics events:
Core Events
| Event Type | Description | Triggered When |
page_viewed | Page navigation | User visits any page |
product_viewed | Product page view | User views product details |
collection_viewed | Collection page view | User browses product collections |
cart_viewed | Shopping cart view | User opens shopping cart |
search_submitted | Search query | User performs search |
Commerce Events
| Event Type | Description | Triggered When |
product_added_to_cart | Add to cart | User adds product to cart |
product_removed_from_cart | Remove from cart | User removes product from cart |
custom_promotion_viewed | Promotion view | User sees promotional content |
The SDK transforms Shopify Analytics events into Glood pixel format using sophisticated data 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
}
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
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
Consent Management
Automatic Consent Checking
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;
}
});
}
Consent Requirements by App
| App | Default Consent Requirements | Description |
| 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
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);
});
});
});
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);
}
}
Consent Errors
// 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
}
// 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
}
});
3. Validate Consent Configuration
// 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