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 thePostHogProvider component:
Server-Side Integration
Server-side tracking uses the PostHog Node.js client:Helper Functions
PostHog Tracking Helper
We provide a reusable helper function for consistent event tracking:- 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 usedactorId (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
actorIdtouserId - ✅ Added PostHog groups for organization-level tracking
- ✅ Created migration script to alias existing duplicates
- ✅ All tracking code updated to use
userId
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
- ✅ 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
- Authentication: User logs in via Auth0
- Context Loading: App loads user context (
userId,organizationId,organizationName) - PostHog Identification: When both user and organization context are available, PostHog:
- Identifies the user with
userIdas distinct ID - Associates the user with their organization via groups
- Identifies the user with
- Event Tracking: All events are tracked using the same
userIdand organization group
User Properties
Each identified user has the following properties:| Property | Source | Description |
|---|---|---|
| Distinct ID | userId | Unique identifier for the person (global, stable) |
| Auth0 user profile | User’s email address | |
| Name | Auth0 user profile | User’s display name |
Organization Tracking via Groups
Organization-level data is tracked using PostHog groups:| Property | Type | Description |
|---|---|---|
| Group Type | organization | Fixed group type for all organizations |
| Group Key | organizationId | Unique identifier for the organization |
| Group Name | organizationName | Display name for the organization |
| Group Slug | organizationSlug | URL-friendly organization identifier |
- 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
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 analysisorganization_name: Human-readable organization identifier- Context-specific data relevant to the event
Best Practices
1. Use the Helper Function
Always usetrackEventWithAuth 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
- Events not appearing: Check that
userIdis available before sending events - Duplicate users: Ensure consistent use of
userIdacross client and server (this was fixed in the migration from actorId) - Missing organization context: Verify that
organizationIdis included in event properties - Events without user information: This happens when server-side events are fired before client-side identification. Always call
posthog.identify()in server actions beforeposthog.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)