Skip to main content

Analytics (PostHog)

CONA uses PostHog for product analytics and user behavior tracking. This guide explains how our PostHog integration works, including user identification, event tracking, and best practices.

Overview

PostHog provides both client-side and server-side analytics tracking, allowing us to understand user behavior across the entire application. Our implementation is designed around CONA’s multi-tenant architecture using the actor/user system.

Integration Architecture

Client-Side Integration

The PostHog client is initialized in the PostHogProvider component:

Server-Side Integration

Server-side tracking uses the PostHog Node.js client:
Note: The server-side PostHog client has different API signatures than the client-side version:

Helper Functions

PostHog Tracking Helper

We provide a reusable helper function for consistent event tracking:
Benefits of using the helper:
  • Automatic user identification with proper fallback logic
  • Consistent organization context
  • Error handling that doesn’t break main operations
  • TypeScript support for better development experience

User Identification System

Migration from actorId to userId (Feb 2026)

Important: We previously used actorId (organization-scoped) as the distinct ID, which created duplicate users in PostHog when the same person belonged to multiple organizations. We have now migrated to using userId (global, stable identifier) as the distinct ID. What changed:
  • ✅ Distinct ID changed from actorId to userId
  • ✅ Added PostHog groups for organization-level tracking
  • ✅ Created migration script to alias existing duplicates
  • ✅ All tracking code updated to use userId
If you need to run the migration:

Why We Use User IDs

CONA uses a multi-tenant architecture where:
  • Users represent the actual person/account (global identity)
  • Actors represent the user’s identity within a specific organization
For PostHog analytics, we use the userId as the distinct ID, not the actorId. This ensures:
  • ✅ The same person is tracked consistently across all organizations
  • ✅ No duplicate user entries when switching organizations
  • ✅ Accurate cross-organization user journey tracking
  • ✅ Organization-level analytics via PostHog groups

Identification Flow

  1. Authentication: User logs in via Auth0
  2. Context Loading: App loads user context (userId, organizationId, organizationName)
  3. PostHog Identification: When both user and organization context are available, PostHog:
    • Identifies the user with userId as distinct ID
    • Associates the user with their organization via groups
  4. Event Tracking: All events are tracked using the same userId and organization group

User Properties

Each identified user has the following properties:

Organization Tracking via Groups

Organization-level data is tracked using PostHog groups: This allows you to:
  • Filter events by organization
  • Track organization-level metrics
  • Understand which organizations are most active
  • Analyze feature adoption per organization
  • Query by organization slug for URL-based filtering

Currently Tracked Events

Organization Management

  • organization_onboarding_completed - When organization setup is complete

App Store & Installation

  • app_installed - When an app is installed from the app store (one-time event)

Integration Management (Business Logic)

  • integration_created - When a new integration is created and configured
  • integration_activated - When an integration is turned on (the important toggle)
  • integration_deactivated - When an integration is turned off (the important toggle)
  • integration_deleted - When an integration is completely removed

Event Separation Logic

We separate App Store events (installation) from Integration events (business usage):
  • App Installation: One-time event when installing from app store
  • Integration Usage: Ongoing business events when users actually use/configure the integration
This prevents double-tracking and focuses on the events that matter for business analytics.

Automatic Events

  • Pageviews: Manually captured with full URL including search params
  • Page Leave: Automatically captured when users navigate away

Event Tracking Examples

Custom Events

Server actions can track custom events using the helper function:

Event Properties

Always include these properties for consistency:
  • organization_id: For organization-specific analysis
  • organization_name: Human-readable organization identifier
  • Context-specific data relevant to the event

Best Practices

1. Use the Helper Function

Always use trackEventWithAuth or trackEvent instead of calling PostHog directly:

2. Include Organization Context

Always include organization information in events:

3. Event Naming Convention

Use consistent event naming:
  • Use snake_case for event names
  • Include the action and object: document_created, user_invited, payment_processed
  • Be specific but not overly verbose

4. Property Naming

  • Use snake_case for property names
  • Include units in property names: amount_cents, duration_seconds
  • Use consistent property names across similar events

5. Error Handling

The helper functions include error handling that won’t break your main operations:

Privacy and Compliance

Data Collection

  • Only collect necessary data for product improvement
  • Respect user privacy settings
  • Follow GDPR and other applicable regulations

Personal Data

  • Email addresses are collected for user identification
  • No sensitive business data should be sent to PostHog
  • Use hashed or anonymized identifiers when possible

Troubleshooting

Common Issues

  1. Events not appearing: Check that userId is available before sending events
  2. Duplicate users: Ensure consistent use of userId across client and server (this was fixed in the migration from actorId)
  3. Missing organization context: Verify that organizationId is included in event properties
  4. Events without user information: This happens when server-side events are fired before client-side identification. Always call posthog.identify() in server actions before posthog.capture()

Timing Issues

Problem: Server-side events fired during onboarding or user creation might not have proper user identification. Solution: Always identify users server-side before capturing events:

Adding New Event Tracking

1. Use the Helper Function

2. Event Naming Guidelines

  • Use descriptive, consistent names
  • Follow the pattern: {object}_{action} (e.g., document_created, integration_activated)
  • Use snake_case for all event names and properties

3. Property Guidelines

  • Include relevant context (IDs, names, counts, boolean flags)
  • Use consistent property naming across similar events
  • Include organization context (handled automatically by helper)
  • Add timestamps when relevant (handled automatically by helper)