User Behavior Analytics for ChatGPT Apps

Understanding how users interact with your ChatGPT app is critical for optimization, retention, and growth. Unlike traditional web analytics, ChatGPT apps require specialized tracking for conversation flows, tool invocations, widget interactions, and multi-turn dialogue patterns. This guide provides production-ready implementations for comprehensive user behavior analytics that respect privacy while delivering actionable insights.

Behavior analytics in ChatGPT apps goes beyond simple pageviews. You need to track tool usage patterns, widget engagement depth, conversation abandonment points, and cross-session user journeys. The conversational nature of ChatGPT creates unique challenges: sessions can span hours, users may invoke multiple tools in a single turn, and success metrics depend on task completion rather than traditional conversion funnels.

This article covers event tracking systems, funnel analysis, cohort segmentation, session replay, and privacy-compliant data collection. Every example uses production TypeScript with proper error handling, type safety, and scalability considerations. Whether you're optimizing a fitness booking app or debugging drop-off in a real estate search tool, these implementations provide the foundation for data-driven decision making.

Event Tracking System

A robust event tracking system captures every significant user interaction with type-safe schemas, automatic batching, and offline support. Here's a production implementation:

// analytics/event-tracker.ts
import { z } from 'zod';

// Event schemas with full type safety
const ToolInvocationEvent = z.object({
  type: z.literal('tool_invocation'),
  timestamp: z.number(),
  userId: z.string(),
  sessionId: z.string(),
  toolName: z.string(),
  parameters: z.record(z.any()),
  duration: z.number(),
  success: z.boolean(),
  errorMessage: z.string().optional(),
});

const WidgetInteractionEvent = z.object({
  type: z.literal('widget_interaction'),
  timestamp: z.number(),
  userId: z.string(),
  sessionId: z.string(),
  widgetId: z.string(),
  action: z.enum(['click', 'submit', 'expand', 'collapse', 'scroll']),
  elementId: z.string().optional(),
  metadata: z.record(z.any()).optional(),
});

const ConversationEvent = z.object({
  type: z.literal('conversation'),
  timestamp: z.number(),
  userId: z.string(),
  sessionId: z.string(),
  event: z.enum(['start', 'turn', 'abandon', 'complete']),
  turnNumber: z.number().optional(),
  intent: z.string().optional(),
  satisfactionScore: z.number().min(1).max(5).optional(),
});

type Event = z.infer<typeof ToolInvocationEvent>
  | z.infer<typeof WidgetInteractionEvent>
  | z.infer<typeof ConversationEvent>;

// Event tracker with batching and offline support
export class EventTracker {
  private queue: Event[] = [];
  private batchSize = 10;
  private flushInterval = 5000; // 5 seconds
  private endpoint: string;
  private flushTimer?: NodeJS.Timeout;

  constructor(endpoint: string) {
    this.endpoint = endpoint;
    this.startAutoFlush();
  }

  // Track tool invocation
  trackToolInvocation(data: {
    toolName: string;
    parameters: Record<string, any>;
    duration: number;
    success: boolean;
    errorMessage?: string;
  }): void {
    const event = {
      type: 'tool_invocation' as const,
      timestamp: Date.now(),
      userId: this.getUserId(),
      sessionId: this.getSessionId(),
      ...data,
    };

    const validated = ToolInvocationEvent.parse(event);
    this.enqueue(validated);
  }

  // Track widget interaction
  trackWidgetInteraction(data: {
    widgetId: string;
    action: 'click' | 'submit' | 'expand' | 'collapse' | 'scroll';
    elementId?: string;
    metadata?: Record<string, any>;
  }): void {
    const event = {
      type: 'widget_interaction' as const,
      timestamp: Date.now(),
      userId: this.getUserId(),
      sessionId: this.getSessionId(),
      ...data,
    };

    const validated = WidgetInteractionEvent.parse(event);
    this.enqueue(validated);
  }

  // Track conversation events
  trackConversation(data: {
    event: 'start' | 'turn' | 'abandon' | 'complete';
    turnNumber?: number;
    intent?: string;
    satisfactionScore?: number;
  }): void {
    const event = {
      type: 'conversation' as const,
      timestamp: Date.now(),
      userId: this.getUserId(),
      sessionId: this.getSessionId(),
      ...data,
    };

    const validated = ConversationEvent.parse(event);
    this.enqueue(validated);
  }

  // Enqueue event and flush if batch full
  private enqueue(event: Event): void {
    this.queue.push(event);

    if (this.queue.length >= this.batchSize) {
      this.flush();
    }
  }

  // Flush events to server
  async flush(): Promise<void> {
    if (this.queue.length === 0) return;

    const batch = [...this.queue];
    this.queue = [];

    try {
      const response = await fetch(this.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ events: batch }),
      });

      if (!response.ok) {
        // Re-queue on failure
        this.queue.unshift(...batch);
        console.error('Failed to send analytics:', response.statusText);
      }
    } catch (error) {
      // Re-queue on network failure
      this.queue.unshift(...batch);
      console.error('Analytics network error:', error);
    }
  }

  // Auto-flush timer
  private startAutoFlush(): void {
    this.flushTimer = setInterval(() => this.flush(), this.flushInterval);
  }

  // Get or create user ID (persistent across sessions)
  private getUserId(): string {
    let userId = localStorage.getItem('analytics_user_id');
    if (!userId) {
      userId = `user_${Date.now()}_${Math.random().toString(36).slice(2)}`;
      localStorage.setItem('analytics_user_id', userId);
    }
    return userId;
  }

  // Get or create session ID (unique per session)
  private getSessionId(): string {
    let sessionId = sessionStorage.getItem('analytics_session_id');
    if (!sessionId) {
      sessionId = `session_${Date.now()}_${Math.random().toString(36).slice(2)}`;
      sessionStorage.setItem('analytics_session_id', sessionId);
    }
    return sessionId;
  }

  // Cleanup on page unload
  destroy(): void {
    if (this.flushTimer) clearInterval(this.flushTimer);
    this.flush();
  }
}

This tracker provides automatic batching to reduce network overhead, offline queueing for poor connectivity, and type-safe event schemas. The persistent user ID enables cross-session analysis while session IDs track individual conversation flows.

Funnel Analysis

Conversion funnels track users through multi-step workflows, identifying where they drop off and what drives completion. Here's a funnel analyzer for ChatGPT apps:

// analytics/funnel-analyzer.ts
import { z } from 'zod';

// Funnel step definition
const FunnelStep = z.object({
  id: z.string(),
  name: z.string(),
  requiredEvents: z.array(z.string()),
  optionalEvents: z.array(z.string()).optional(),
  timeoutMinutes: z.number().optional(),
});

type FunnelStep = z.infer<typeof FunnelStep>;

// Funnel configuration
const FunnelConfig = z.object({
  id: z.string(),
  name: z.string(),
  steps: z.array(FunnelStep),
  conversionWindowMinutes: z.number().default(60),
});

type FunnelConfig = z.infer<typeof FunnelConfig>;

// User journey through funnel
interface UserJourney {
  userId: string;
  sessionId: string;
  startTime: number;
  currentStep: number;
  events: Array<{
    timestamp: number;
    type: string;
    data: any;
  }>;
  completed: boolean;
}

export class FunnelAnalyzer {
  private funnels = new Map<string, FunnelConfig>();
  private journeys = new Map<string, UserJourney>();

  // Register funnel configuration
  registerFunnel(config: FunnelConfig): void {
    const validated = FunnelConfig.parse(config);
    this.funnels.set(validated.id, validated);
  }

  // Track event and update funnel progress
  trackEvent(
    funnelId: string,
    userId: string,
    sessionId: string,
    eventType: string,
    eventData: any
  ): void {
    const funnel = this.funnels.get(funnelId);
    if (!funnel) {
      console.error(`Funnel ${funnelId} not found`);
      return;
    }

    const journeyKey = `${funnelId}_${userId}_${sessionId}`;
    let journey = this.journeys.get(journeyKey);

    // Create new journey if none exists
    if (!journey) {
      journey = {
        userId,
        sessionId,
        startTime: Date.now(),
        currentStep: 0,
        events: [],
        completed: false,
      };
      this.journeys.set(journeyKey, journey);
    }

    // Add event to journey
    journey.events.push({
      timestamp: Date.now(),
      type: eventType,
      data: eventData,
    });

    // Check if event advances funnel
    this.advanceFunnel(funnel, journey, eventType);
  }

  // Advance user through funnel steps
  private advanceFunnel(
    funnel: FunnelConfig,
    journey: UserJourney,
    eventType: string
  ): void {
    if (journey.completed) return;

    const currentStep = funnel.steps[journey.currentStep];
    if (!currentStep) return;

    // Check if event matches required events
    if (currentStep.requiredEvents.includes(eventType)) {
      // Check timeout
      const lastEventTime = journey.events[journey.events.length - 2]?.timestamp || journey.startTime;
      const minutesSinceLastEvent = (Date.now() - lastEventTime) / 1000 / 60;

      if (currentStep.timeoutMinutes && minutesSinceLastEvent > currentStep.timeoutMinutes) {
        // Timeout - abandon journey
        this.journeys.delete(`${funnel.id}_${journey.userId}_${journey.sessionId}`);
        this.sendFunnelEvent(funnel.id, journey, 'abandon', currentStep.id);
        return;
      }

      // Advance to next step
      journey.currentStep++;
      this.sendFunnelEvent(funnel.id, journey, 'advance', currentStep.id);

      // Check if funnel complete
      if (journey.currentStep >= funnel.steps.length) {
        journey.completed = true;
        this.sendFunnelEvent(funnel.id, journey, 'complete', 'final');
      }
    }
  }

  // Calculate funnel metrics
  async getFunnelMetrics(funnelId: string, startDate: Date, endDate: Date): Promise<{
    totalStarts: number;
    completions: number;
    conversionRate: number;
    stepMetrics: Array<{
      stepId: string;
      stepName: string;
      entries: number;
      exits: number;
      dropoffRate: number;
      avgTimeToNext: number;
    }>;
  }> {
    const funnel = this.funnels.get(funnelId);
    if (!funnel) throw new Error(`Funnel ${funnelId} not found`);

    // Fetch journey data from analytics database
    const journeys = await this.fetchJourneys(funnelId, startDate, endDate);

    const totalStarts = journeys.length;
    const completions = journeys.filter(j => j.completed).length;
    const conversionRate = totalStarts > 0 ? completions / totalStarts : 0;

    const stepMetrics = funnel.steps.map((step, index) => {
      const entries = journeys.filter(j => j.currentStep >= index).length;
      const exits = journeys.filter(j => j.currentStep === index && !j.completed).length;
      const dropoffRate = entries > 0 ? exits / entries : 0;

      // Calculate average time to next step
      const timesToNext = journeys
        .filter(j => j.currentStep > index)
        .map(j => {
          const stepEvents = j.events.filter(e =>
            step.requiredEvents.includes(e.type)
          );
          if (stepEvents.length < 2) return null;
          return stepEvents[1].timestamp - stepEvents[0].timestamp;
        })
        .filter((t): t is number => t !== null);

      const avgTimeToNext = timesToNext.length > 0
        ? timesToNext.reduce((a, b) => a + b, 0) / timesToNext.length
        : 0;

      return {
        stepId: step.id,
        stepName: step.name,
        entries,
        exits,
        dropoffRate,
        avgTimeToNext: avgTimeToNext / 1000 / 60, // minutes
      };
    });

    return {
      totalStarts,
      completions,
      conversionRate,
      stepMetrics,
    };
  }

  // Send funnel event to analytics
  private sendFunnelEvent(
    funnelId: string,
    journey: UserJourney,
    event: 'advance' | 'abandon' | 'complete',
    stepId: string
  ): void {
    // Implementation would send to analytics endpoint
    console.log('Funnel event:', { funnelId, userId: journey.userId, event, stepId });
  }

  // Fetch journeys from database (placeholder)
  private async fetchJourneys(
    funnelId: string,
    startDate: Date,
    endDate: Date
  ): Promise<UserJourney[]> {
    // Implementation would query analytics database
    return [];
  }
}

Use this analyzer to track multi-tool workflows, identify drop-off points, and optimize conversion rates through data-driven UX improvements.

Cohort Analysis

Cohort analysis segments users by shared characteristics and tracks retention, activation, and engagement over time:

// analytics/cohort-analyzer.ts
import { z } from 'zod';

// Cohort definition
const Cohort = z.object({
  id: z.string(),
  name: z.string(),
  definition: z.object({
    type: z.enum(['acquisition', 'behavior', 'attribute']),
    criteria: z.record(z.any()),
    startDate: z.string(),
    endDate: z.string().optional(),
  }),
});

type Cohort = z.infer<typeof Cohort>;

// User activity
interface UserActivity {
  userId: string;
  firstSeen: number;
  lastSeen: number;
  totalSessions: number;
  totalToolInvocations: number;
  totalWidgetInteractions: number;
  attributes: Record<string, any>;
}

export class CohortAnalyzer {
  private cohorts = new Map<string, Cohort>();

  // Define cohort
  defineCohort(cohort: Cohort): void {
    const validated = Cohort.parse(cohort);
    this.cohorts.set(validated.id, validated);
  }

  // Calculate retention metrics
  async getRetentionMetrics(
    cohortId: string,
    periodDays: number = 7
  ): Promise<{
    cohortSize: number;
    retentionByPeriod: Array<{
      period: number;
      activeUsers: number;
      retentionRate: number;
    }>;
  }> {
    const cohort = this.cohorts.get(cohortId);
    if (!cohort) throw new Error(`Cohort ${cohortId} not found`);

    const users = await this.fetchCohortUsers(cohort);
    const cohortSize = users.length;

    const startDate = new Date(cohort.definition.startDate);
    const periods = 12; // Track 12 periods

    const retentionByPeriod = [];
    for (let period = 0; period < periods; period++) {
      const periodStart = new Date(startDate);
      periodStart.setDate(periodStart.getDate() + period * periodDays);

      const periodEnd = new Date(periodStart);
      periodEnd.setDate(periodEnd.getDate() + periodDays);

      const activeUsers = users.filter(user => {
        const lastSeen = new Date(user.lastSeen);
        return lastSeen >= periodStart && lastSeen < periodEnd;
      }).length;

      retentionByPeriod.push({
        period,
        activeUsers,
        retentionRate: cohortSize > 0 ? activeUsers / cohortSize : 0,
      });
    }

    return { cohortSize, retentionByPeriod };
  }

  // Calculate activation metrics
  async getActivationMetrics(
    cohortId: string,
    activationCriteria: {
      minSessions?: number;
      minToolInvocations?: number;
      minWidgetInteractions?: number;
    }
  ): Promise<{
    cohortSize: number;
    activatedUsers: number;
    activationRate: number;
    avgTimeToActivation: number;
  }> {
    const cohort = this.cohorts.get(cohortId);
    if (!cohort) throw new Error(`Cohort ${cohortId} not found`);

    const users = await this.fetchCohortUsers(cohort);
    const cohortSize = users.length;

    const activatedUsers = users.filter(user => {
      if (activationCriteria.minSessions && user.totalSessions < activationCriteria.minSessions) {
        return false;
      }
      if (activationCriteria.minToolInvocations && user.totalToolInvocations < activationCriteria.minToolInvocations) {
        return false;
      }
      if (activationCriteria.minWidgetInteractions && user.totalWidgetInteractions < activationCriteria.minWidgetInteractions) {
        return false;
      }
      return true;
    });

    const timesToActivation = activatedUsers.map(user =>
      user.lastSeen - user.firstSeen
    );

    const avgTimeToActivation = timesToActivation.length > 0
      ? timesToActivation.reduce((a, b) => a + b, 0) / timesToActivation.length / 1000 / 60 / 60 // hours
      : 0;

    return {
      cohortSize,
      activatedUsers: activatedUsers.length,
      activationRate: cohortSize > 0 ? activatedUsers.length / cohortSize : 0,
      avgTimeToActivation,
    };
  }

  // Fetch cohort users (placeholder)
  private async fetchCohortUsers(cohort: Cohort): Promise<UserActivity[]> {
    // Implementation would query analytics database based on cohort criteria
    return [];
  }
}

Cohort analysis reveals patterns like "users who try 3+ tools in their first session have 4x higher retention" or "weekend signups activate 30% faster than weekday signups."

Session Replay and Heatmaps

Session replay reconstructs user journeys by capturing interaction sequences, tool invocation patterns, and widget engagement:

// analytics/session-recorder.ts
import { z } from 'zod';

// Interaction event
const InteractionEvent = z.object({
  timestamp: z.number(),
  type: z.enum(['click', 'scroll', 'tool_invocation', 'widget_open', 'widget_close', 'input']),
  target: z.string(),
  coordinates: z.object({ x: z.number(), y: z.number() }).optional(),
  metadata: z.record(z.any()).optional(),
});

type InteractionEvent = z.infer<typeof InteractionEvent>;

// Session recording
interface SessionRecording {
  sessionId: string;
  userId: string;
  startTime: number;
  endTime?: number;
  events: InteractionEvent[];
  metadata: {
    userAgent: string;
    viewport: { width: number; height: number };
    referrer: string;
  };
}

export class SessionRecorder {
  private recording: SessionRecording | null = null;
  private eventBuffer: InteractionEvent[] = [];
  private bufferSize = 50;
  private endpoint: string;

  constructor(endpoint: string) {
    this.endpoint = endpoint;
  }

  // Start recording session
  startRecording(userId: string, sessionId: string): void {
    this.recording = {
      sessionId,
      userId,
      startTime: Date.now(),
      events: [],
      metadata: {
        userAgent: navigator.userAgent,
        viewport: {
          width: window.innerWidth,
          height: window.innerHeight,
        },
        referrer: document.referrer,
      },
    };

    // Attach event listeners
    this.attachListeners();
  }

  // Stop recording and save
  async stopRecording(): Promise<void> {
    if (!this.recording) return;

    this.recording.endTime = Date.now();
    await this.flushEvents();

    // Save complete recording
    await this.saveRecording(this.recording);
    this.recording = null;
  }

  // Record interaction event
  recordEvent(event: Omit<InteractionEvent, 'timestamp'>): void {
    if (!this.recording) return;

    const timestampedEvent = {
      ...event,
      timestamp: Date.now(),
    };

    const validated = InteractionEvent.parse(timestampedEvent);
    this.eventBuffer.push(validated);

    if (this.eventBuffer.length >= this.bufferSize) {
      this.flushEvents();
    }
  }

  // Attach DOM event listeners
  private attachListeners(): void {
    // Click tracking
    document.addEventListener('click', (e) => {
      this.recordEvent({
        type: 'click',
        target: this.getElementPath(e.target as Element),
        coordinates: { x: e.clientX, y: e.clientY },
      });
    });

    // Scroll tracking (throttled)
    let scrollTimeout: NodeJS.Timeout;
    document.addEventListener('scroll', () => {
      clearTimeout(scrollTimeout);
      scrollTimeout = setTimeout(() => {
        this.recordEvent({
          type: 'scroll',
          target: 'window',
          coordinates: { x: window.scrollX, y: window.scrollY },
        });
      }, 100);
    });
  }

  // Get CSS selector path for element
  private getElementPath(element: Element): string {
    const path: string[] = [];
    let current: Element | null = element;

    while (current && current !== document.body) {
      let selector = current.tagName.toLowerCase();

      if (current.id) {
        selector += `#${current.id}`;
        path.unshift(selector);
        break;
      }

      const parent = current.parentElement;
      if (parent) {
        const siblings = Array.from(parent.children);
        const index = siblings.indexOf(current);
        if (siblings.filter(s => s.tagName === current.tagName).length > 1) {
          selector += `:nth-child(${index + 1})`;
        }
      }

      path.unshift(selector);
      current = parent;
    }

    return path.join(' > ');
  }

  // Flush event buffer
  private async flushEvents(): Promise<void> {
    if (!this.recording || this.eventBuffer.length === 0) return;

    this.recording.events.push(...this.eventBuffer);
    this.eventBuffer = [];
  }

  // Save recording to server
  private async saveRecording(recording: SessionRecording): Promise<void> {
    try {
      await fetch(this.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(recording),
      });
    } catch (error) {
      console.error('Failed to save session recording:', error);
    }
  }
}

// Heatmap generator
export class HeatmapGenerator {
  // Generate click heatmap data
  static generateClickHeatmap(recordings: SessionRecording[]): Array<{
    x: number;
    y: number;
    intensity: number;
  }> {
    const clickMap = new Map<string, number>();

    // Aggregate clicks
    recordings.forEach(recording => {
      recording.events
        .filter(e => e.type === 'click' && e.coordinates)
        .forEach(event => {
          const key = `${Math.floor(event.coordinates!.x / 10)}_${Math.floor(event.coordinates!.y / 10)}`;
          clickMap.set(key, (clickMap.get(key) || 0) + 1);
        });
    });

    // Convert to heatmap points
    const maxClicks = Math.max(...clickMap.values());
    return Array.from(clickMap.entries()).map(([key, count]) => {
      const [x, y] = key.split('_').map(Number);
      return {
        x: x * 10,
        y: y * 10,
        intensity: count / maxClicks,
      };
    });
  }
}

Session replay helps you understand "why" users behave certain ways by showing their actual interaction sequences rather than just aggregate metrics.

Privacy Considerations and GDPR Compliance

User behavior analytics must respect privacy regulations and user consent. Here's a privacy-compliant implementation:

// analytics/privacy-manager.ts
import { z } from 'zod';

// Consent preferences
const ConsentPreferences = z.object({
  analytics: z.boolean(),
  sessionRecording: z.boolean(),
  personalization: z.boolean(),
  marketing: z.boolean(),
});

type ConsentPreferences = z.infer<typeof ConsentPreferences>;

// Data anonymizer
export class DataAnonymizer {
  // Anonymize user ID
  static anonymizeUserId(userId: string, salt: string): string {
    return this.hash(`${userId}_${salt}`);
  }

  // Anonymize IP address
  static anonymizeIP(ip: string): string {
    const parts = ip.split('.');
    if (parts.length === 4) {
      // IPv4: mask last octet
      return `${parts[0]}.${parts[1]}.${parts[2]}.0`;
    } else {
      // IPv6: mask last 80 bits
      const segments = ip.split(':');
      return `${segments.slice(0, 3).join(':')}::`;
    }
  }

  // Remove PII from event data
  static sanitizeEventData(data: any): any {
    const sanitized = { ...data };
    const piiFields = ['email', 'phone', 'ssn', 'creditCard', 'password'];

    piiFields.forEach(field => {
      if (field in sanitized) {
        delete sanitized[field];
      }
    });

    return sanitized;
  }

  // Simple hash function
  private static hash(input: string): string {
    let hash = 0;
    for (let i = 0; i < input.length; i++) {
      const char = input.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return Math.abs(hash).toString(36);
  }
}

// Consent manager
export class ConsentManager {
  private preferences: ConsentPreferences | null = null;
  private storageKey = 'analytics_consent';

  // Load consent preferences
  loadPreferences(): ConsentPreferences | null {
    const stored = localStorage.getItem(this.storageKey);
    if (!stored) return null;

    try {
      const parsed = JSON.parse(stored);
      this.preferences = ConsentPreferences.parse(parsed);
      return this.preferences;
    } catch {
      return null;
    }
  }

  // Save consent preferences
  savePreferences(preferences: ConsentPreferences): void {
    const validated = ConsentPreferences.parse(preferences);
    this.preferences = validated;
    localStorage.setItem(this.storageKey, JSON.stringify(validated));
  }

  // Check if analytics allowed
  canTrackAnalytics(): boolean {
    return this.preferences?.analytics ?? false;
  }

  // Check if session recording allowed
  canRecordSessions(): boolean {
    return this.preferences?.sessionRecording ?? false;
  }

  // Request consent (show banner)
  async requestConsent(): Promise<ConsentPreferences> {
    // Show consent banner and wait for user response
    return new Promise((resolve) => {
      // Implementation would show UI banner
      const defaultPreferences: ConsentPreferences = {
        analytics: false,
        sessionRecording: false,
        personalization: false,
        marketing: false,
      };
      resolve(defaultPreferences);
    });
  }
}

Always obtain explicit consent before tracking, anonymize data where possible, provide clear opt-out mechanisms, and honor data deletion requests promptly.

Analytics Dashboard Implementation

Here's a complete analytics dashboard that ties everything together:

// analytics/dashboard.ts
import { EventTracker } from './event-tracker';
import { FunnelAnalyzer } from './funnel-analyzer';
import { CohortAnalyzer } from './cohort-analyzer';
import { ConsentManager, DataAnonymizer } from './privacy-manager';

export class AnalyticsDashboard {
  private eventTracker: EventTracker;
  private funnelAnalyzer: FunnelAnalyzer;
  private cohortAnalyzer: CohortAnalyzer;
  private consentManager: ConsentManager;

  constructor(analyticsEndpoint: string) {
    this.consentManager = new ConsentManager();
    this.eventTracker = new EventTracker(analyticsEndpoint);
    this.funnelAnalyzer = new FunnelAnalyzer();
    this.cohortAnalyzer = new CohortAnalyzer();

    this.initializeAnalytics();
  }

  // Initialize analytics with consent check
  private async initializeAnalytics(): Promise<void> {
    let preferences = this.consentManager.loadPreferences();

    if (!preferences) {
      preferences = await this.consentManager.requestConsent();
      this.consentManager.savePreferences(preferences);
    }

    if (preferences.analytics) {
      this.setupEventTracking();
    }
  }

  // Setup automatic event tracking
  private setupEventTracking(): void {
    // Track tool invocations
    window.addEventListener('tool-invoked', ((e: CustomEvent) => {
      this.eventTracker.trackToolInvocation({
        toolName: e.detail.name,
        parameters: DataAnonymizer.sanitizeEventData(e.detail.parameters),
        duration: e.detail.duration,
        success: e.detail.success,
        errorMessage: e.detail.error,
      });
    }) as EventListener);

    // Track widget interactions
    window.addEventListener('widget-interaction', ((e: CustomEvent) => {
      this.eventTracker.trackWidgetInteraction({
        widgetId: e.detail.widgetId,
        action: e.detail.action,
        elementId: e.detail.elementId,
        metadata: DataAnonymizer.sanitizeEventData(e.detail.metadata),
      });
    }) as EventListener);

    // Track conversation events
    window.addEventListener('conversation-event', ((e: CustomEvent) => {
      this.eventTracker.trackConversation({
        event: e.detail.event,
        turnNumber: e.detail.turnNumber,
        intent: e.detail.intent,
        satisfactionScore: e.detail.satisfactionScore,
      });
    }) as EventListener);
  }

  // Get comprehensive dashboard metrics
  async getDashboardMetrics(dateRange: { start: Date; end: Date }): Promise<{
    overview: {
      totalUsers: number;
      activeUsers: number;
      totalSessions: number;
      avgSessionDuration: number;
    };
    funnels: Array<{
      funnelId: string;
      conversionRate: number;
      dropoffPoints: Array<{ step: string; rate: number }>;
    }>;
    cohorts: Array<{
      cohortId: string;
      retentionRate: number;
      activationRate: number;
    }>;
    topTools: Array<{
      toolName: string;
      invocations: number;
      successRate: number;
    }>;
  }> {
    // Implementation would aggregate data from all analyzers
    return {
      overview: {
        totalUsers: 0,
        activeUsers: 0,
        totalSessions: 0,
        avgSessionDuration: 0,
      },
      funnels: [],
      cohorts: [],
      topTools: [],
    };
  }
}

This dashboard provides a unified interface for all analytics capabilities while respecting user privacy preferences.

Conclusion

User behavior analytics transforms ChatGPT apps from black boxes into optimizable systems. By tracking tool invocations, widget interactions, and conversation flows, you gain insights that drive retention, activation, and conversion improvements. The production TypeScript implementations in this guide provide type-safe, privacy-compliant analytics that scale from prototypes to production.

Key takeaways: implement comprehensive event tracking with automatic batching, analyze conversion funnels to identify drop-off points, segment users into cohorts for retention analysis, use session replay to understand the "why" behind metrics, and always respect privacy through proper consent management and data anonymization.

Ready to implement world-class analytics in your ChatGPT app? MakeAIHQ provides built-in analytics tracking, funnel visualization, and cohort analysis for all ChatGPT apps created on our platform. Build data-driven conversational experiences without writing analytics code. Start your free trial today and deploy your first analytics-enabled ChatGPT app in 48 hours.


Related Resources:

  • Complete Guide to ChatGPT App Analytics and Optimization
  • Google Analytics 4 Setup for ChatGPT Apps
  • Conversion Optimization Strategies for ChatGPT Apps
  • GDPR Compliance for ChatGPT Analytics
  • A/B Testing ChatGPT Widget Designs
  • Real-time Analytics Dashboard Implementation
  • Privacy-Compliant Session Recording

External References: