Skip to main content
Imagine you’re running a B2B SaaS product — think a project management or analytics tool where companies sign up, invite their team, and pay by subscription. This guide walks through tracking both individual user actions and account-level activity: from signup and activation through feature usage, team collaboration, plan upgrades, and early churn signals.

The SaaS Event Model

Two-Level Tracking: User + Account

Unlike B2C, SaaS requires tracking both:
  1. Individual users (seats, roles, activity)
  2. Accounts/Organizations (subscriptions, usage limits, billing)
Implementation:
// Track at user level
track('Feature Used', {
  feature_name: 'custom_reports',
  user_role: 'admin',
  account_id: 'acct_123',  // Link to account
  workspace_id: 'ws_456'
});

// Track at account level (server-side)
track('Account Upgraded', {
  account_id: 'acct_123',
  from_plan: 'professional',
  to_plan: 'enterprise',
  seat_count: 25,
  mrr_change: 500,
  contract_value: 6000  // Annual contract
});
Profile attributes:
// User-level
setProfileAttributes({
  account_id: 'acct_123',
  role: 'admin',
  department: 'marketing',
  invited_by: 'user_owner_789',
  activated: true,
  activation_date: '2026-05-01T10:00:00Z'
});

// Account-level (stored separately)
setAccountAttributes('acct_123', {
  plan_type: 'enterprise',
  mrr: 1200,
  seat_count: 25,
  seats_occupied: 18,
  trial_ends_at: null,  // No longer on trial
  billing_status: 'active',
  created_at: '2025-01-15T10:00:00Z',
  industry: 'E-commerce',
  company_size: '100-500'
});

Activation Events (PLG Focus)

Signup Completed

When: User creates account (self-serve signup)
// Client-side
identify({
  userId: 'user_12345',
  traits: {
    email: 'john@acme.com',
    name: 'John Smith',
    company: 'Acme Corp',
    role: 'Marketing Manager',
    signup_source: 'organic_search',
    signup_flow: 'free_trial',  // vs 'demo_request', 'sales_led'
    signup_page: '/pricing'
  }
});

track('Signup Completed', {
  account_id: 'acct_123',  // Auto-generated or from invite link
  is_account_creator: true,
  trial_plan: 'professional',
  trial_duration_days: 14,
  signup_time_seconds: 45,  // Time from form start to completion
  utm_source: 'google',
  utm_campaign: 'spring_2026_search'
});
Trigger: Welcome email + onboarding sequence

Activation Milestone Events

Why activation matters: Activated users = 3x higher retention, 5x higher conversion to paid. Define your activation criteria (example for project management tool):
  • Created first project
  • Invited team member
  • Created first task
  • Assigned task to someone
  • Completed at least one task
Track each milestone:
// Milestone 1: First project created
track('First Project Created', {
  project_name: 'Q2 Marketing Campaign',
  project_type: 'marketing',
  days_since_signup: 0,  // Happened immediately
  time_to_milestone_minutes: 5
});

setProfileAttributes({
  activation_step: 1,
  activation_milestone_1_completed: true,
  activation_milestone_1_at: '2026-05-04T10:05:00Z'
});

// Milestone 2: First team member invited
track('First Team Member Invited', {
  invitee_email: 'sarah@acme.com',
  invitee_role: 'member',
  days_since_signup: 0,
  time_to_milestone_minutes: 12,
  invite_method: 'email'
});

setProfileAttributes({
  activation_step: 2,
  activation_milestone_2_completed: true,
  activation_milestone_2_at: '2026-05-04T10:12:00Z'
});

// ... continue for all milestones

// Final activation
track('User Activated', {
  days_to_activation: 0,  // Activated same day as signup
  time_to_activation_minutes: 45,
  activation_path: 'power_user',  // vs 'slow_adopter', 'invited_user'
  milestones_completed: 5
});

setProfileAttributes({
  activated: true,
  activation_date: '2026-05-04T10:45:00Z',
  activation_cohort: '2026-05-W18'  // Week 18 of 2026
});
Analytics: Activation funnel
SELECT 
  COUNT(DISTINCT CASE WHEN event_name = 'Signup Completed' THEN user_id END) AS signups,
  COUNT(DISTINCT CASE WHEN event_name = 'First Project Created' THEN user_id END) AS milestone_1,
  COUNT(DISTINCT CASE WHEN event_name = 'First Team Member Invited' THEN user_id END) AS milestone_2,
  COUNT(DISTINCT CASE WHEN event_name = 'First Task Created' THEN user_id END) AS milestone_3,
  COUNT(DISTINCT CASE WHEN event_name = 'User Activated' THEN user_id END) AS activated
FROM profile_events
WHERE workspace_id = 'ws_123'
AND timestamp >= DATE_TRUNC('month', NOW());

Feature Usage Events

Core Feature Used

When: User engages with key product feature
track('Report Generated', {
  report_type: 'custom',  // vs 'template', 'scheduled'
  report_name: 'Monthly Sales Performance',
  data_range: 'last_30_days',
  filters_applied: ['region: US', 'product_category: Electronics'],
  visualization_types: ['line_chart', 'bar_chart', 'data_table'],
  export_format: 'pdf',  // vs 'csv', 'excel', null (viewed only)
  generation_time_seconds: 2.3,
  row_count: 15420,
  is_first_use: false
});
Use cases:
  • Usage-based pricing: Track API calls, reports generated, emails sent
  • Feature adoption: % of users who’ve used Feature X
  • Power user identification: Users who generate 10+ reports/month

Premium Feature Gated

When: Free/trial user hits paywall
track('Premium Feature Gated', {
  feature_name: 'advanced_analytics',
  user_plan: 'free',
  required_plan: 'professional',
  gate_type: 'soft',  // Showed upgrade CTA vs 'hard' (feature disabled)
  interaction: 'clicked_upgrade',  // vs 'dismissed', 'closed'
  days_since_signup: 7,
  usage_this_month: 145  // Free tier allows 100
});
Trigger: Upgrade prompt email or in-app message

Collaboration Events

Team collaboration = higher retention + account expansion

Team Member Invited

track('Team Member Invited', {
  invitee_email: 'sarah@acme.com',
  invitee_role: 'member',  // vs 'admin', 'viewer'
  invited_by: 'user_12345',
  invite_method: 'email',  // vs 'link', 'bulk_csv'
  account_seat_count: 5,
  seats_occupied_before: 2,
  seats_occupied_after: 3
});

Team Member Joined

// When invitee accepts invitation
identify({
  userId: 'user_67890',
  traits: {
    email: 'sarah@acme.com',
    account_id: 'acct_123',
    role: 'member',
    invited_by: 'user_12345',
    joined_via: 'email_invite'
  }
});

track('Team Member Joined', {
  account_id: 'acct_123',
  user_role: 'member',
  invited_by: 'user_12345',
  days_to_accept: 1,  // Time from invite to acceptance
  seats_occupied: 3,
  seat_limit: 5
});

Mentioned User

track('User Mentioned', {
  mentioned_user_id: 'user_67890',
  mention_context: 'task_comment',  // vs 'document', 'chat'
  entity_id: 'task_456',
  entity_type: 'task'
});
Trigger: Notification to mentioned user

Shared Content

track('Content Shared', {
  content_type: 'dashboard',
  content_id: 'dash_789',
  shared_with: ['user_67890', 'user_11111'],
  permission_level: 'view',  // vs 'edit', 'admin'
  share_method: 'internal',  // vs 'public_link', 'email'
});
Analytics: Collaboration index = (mentions + shares + comments) / user count

Usage-Based Pricing Events

Quota Tracked

For usage-based billing (API calls, storage, seats, etc.)
// Server-side event (triggered by API gateway)
track('API Call Made', {
  account_id: 'acct_123',
  endpoint: '/api/v1/analytics/reports',
  method: 'POST',
  user_id: 'user_12345',
  response_time_ms: 145,
  status_code: 200,
  quota_consumed: 1,  // 1 API call
  quota_remaining: 4850,  // 150 calls left this month
  quota_limit: 5000,
  billing_cycle: '2026-05'
});

Quota Warning Triggered

When: User approaching usage limit
// Server-side (quota monitoring service)
if (accountUsage.apiCalls >= accountUsage.quota * 0.8) {
  track('Quota Warning Triggered', {
    account_id: 'acct_123',
    quota_type: 'api_calls',
    quota_used: 4000,
    quota_limit: 5000,
    quota_percentage: 80,
    warning_level: '80_percent',  // vs '90_percent', '100_percent'
    billing_cycle: '2026-05',
    days_left_in_cycle: 10
  });
  
  // Trigger email to account admin
}
Trigger: Email to admins + in-app banner

Quota Exceeded

When: User hits hard limit
track('Quota Exceeded', {
  account_id: 'acct_123',
  quota_type: 'api_calls',
  quota_limit: 5000,
  attempted_action: 'generate_report',
  days_left_in_cycle: 8,
  overage_by: 1,
  current_plan: 'professional',
  upgrade_offer_shown: true
});
Trigger: Upgrade prompt + sales notification (high-value account)

Account Expansion Events

Seat Added

When: Account increases seat count
track('Seat Added', {
  account_id: 'acct_123',
  seats_before: 5,
  seats_after: 10,
  added_by: 'user_12345',  // Account admin
  reason: 'new_hires',  // vs 'seasonal_growth', 'expansion'
  mrr_change: 200,  // $40/seat/month * 5 seats
  prorated_charge: 150  // Mid-cycle addition
});

Plan Upgraded

When: Account moves to higher tier
track('Plan Upgraded', {
  account_id: 'acct_123',
  from_plan: 'professional',
  to_plan: 'enterprise',
  upgrade_trigger: 'quota_exceeded',  // vs 'sales_call', 'self_serve'
  seat_count: 10,
  mrr_before: 400,
  mrr_after: 1200,
  contract_length_months: 12,
  contract_value: 14400,
  discount_applied: 10,  // 10% annual discount
  upgraded_by: 'user_12345',
  sales_rep: 'rep_sarah_jones'  // If sales-assisted
});

// Update account attributes
setAccountAttributes('acct_123', {
  plan_type: 'enterprise',
  mrr: 1200,
  contract_value: 14400,
  billing_cycle: 'annual',
  upgraded_at: '2026-05-04T10:00:00Z'
});
Trigger: Thank-you email + account manager introduction (enterprise)

Feature Tier Unlocked

When: Upgrade unlocks new features
track('Feature Tier Unlocked', {
  account_id: 'acct_123',
  plan: 'enterprise',
  features_unlocked: [
    'sso_authentication',
    'custom_reports',
    'api_access',
    'dedicated_support'
  ],
  previous_limitations: {
    'reports_per_month': 50,
    'api_calls_per_month': 5000
  },
  new_limits: {
    'reports_per_month': 'unlimited',
    'api_calls_per_month': 50000
  }
});
Trigger: Feature announcement email + onboarding guide

Churn Signals

Negative Engagement Events

Track behaviors indicating churn risk:

Usage Dropped

// Server-side (daily cron job)
const lastWeekActivity = getActivityCount(accountId, 'last_7_days');
const previousWeekActivity = getActivityCount(accountId, '8_to_14_days_ago');

if (lastWeekActivity < previousWeekActivity * 0.5) {
  track('Usage Dropped Significantly', {
    account_id: accountId,
    last_week_activity: lastWeekActivity,
    previous_week_activity: previousWeekActivity,
    drop_percentage: 60,
    primary_users_inactive: ['user_12345', 'user_67890'],
    plan_type: 'professional',
    mrr: 400
  });
}
Trigger: Outreach email from customer success

Cancellation Flow Started

track('Cancellation Flow Started', {
  account_id: 'acct_123',
  initiated_by: 'user_12345',
  user_role: 'admin',
  plan_type: 'professional',
  mrr: 400,
  account_age_days: 180,
  last_active_days_ago: 14,
  retention_offer_shown: true,
  retention_offer_type: '20_percent_discount'
});
Trigger: Save workflow (discount offer, feature unlock, account review call)

Cancellation Reason Selected

track('Cancellation Reason Selected', {
  account_id: 'acct_123',
  reason: 'too_expensive',  // vs 'missing_features', 'switching_to_competitor', 'no_longer_needed'
  reason_details: 'Budget cuts this quarter',
  competitor_name: null,  // Or 'Competitor X' if switching
  requested_export: true,
  requested_refund: false,
  feedback_provided: true
});
Analytics: Top churn reasons by plan type, MRR cohort

Account Downgraded

track('Plan Downgraded', {
  account_id: 'acct_123',
  from_plan: 'professional',
  to_plan: 'starter',
  downgrade_reason: 'budget_constraints',
  seat_count_before: 10,
  seat_count_after: 5,
  mrr_before: 400,
  mrr_after: 150,
  mrr_change: -250,
  downgraded_by: 'user_12345',
  features_lost: ['custom_reports', 'api_access']
});
Trigger: Win-back campaign (show ROI of lost features)

Analytics Queries

Activation Rate by Signup Source

SELECT 
  properties->>'signup_source' AS source,
  COUNT(DISTINCT CASE WHEN event_name = 'Signup Completed' THEN user_id END) AS signups,
  COUNT(DISTINCT CASE WHEN event_name = 'User Activated' THEN user_id END) AS activated,
  ROUND(100.0 * COUNT(DISTINCT CASE WHEN event_name = 'User Activated' THEN user_id END) /
        COUNT(DISTINCT CASE WHEN event_name = 'Signup Completed' THEN user_id END), 2) AS activation_rate
FROM profile_events
WHERE workspace_id = 'ws_123'
AND timestamp >= DATE_TRUNC('month', NOW())
GROUP BY source
ORDER BY activation_rate DESC;

Feature Adoption by Plan Type

SELECT 
  u.traits->>'plan_type' AS plan,
  COUNT(DISTINCT e.user_id) AS total_users,
  COUNT(DISTINCT CASE WHEN e.event_name = 'Report Generated' THEN e.user_id END) AS used_reports,
  COUNT(DISTINCT CASE WHEN e.event_name = 'API Call Made' THEN e.user_id END) AS used_api,
  COUNT(DISTINCT CASE WHEN e.event_name = 'Dashboard Created' THEN e.user_id END) AS used_dashboards,
  ROUND(100.0 * COUNT(DISTINCT CASE WHEN e.event_name = 'Report Generated' THEN e.user_id END) / COUNT(DISTINCT e.user_id), 2) AS reports_adoption_pct
FROM profiles u
LEFT JOIN profile_events e ON u.user_id = e.user_id
WHERE u.workspace_id = 'ws_123'
AND e.timestamp >= NOW() - INTERVAL 30 DAY
GROUP BY plan
ORDER BY plan;

Revenue Expansion by Cohort

WITH cohorts AS (
  SELECT 
    user_id,
    DATE_TRUNC('month', traits->>'created_at'::TIMESTAMP) AS cohort_month
  FROM profiles
  WHERE workspace_id = 'ws_123'
),
upgrades AS (
  SELECT 
    user_id,
    (properties->>'mrr_change')::DECIMAL AS mrr_change,
    timestamp
  FROM profile_events
  WHERE event_name = 'Plan Upgraded'
  AND workspace_id = 'ws_123'
)
SELECT 
  c.cohort_month,
  COUNT(DISTINCT c.user_id) AS cohort_size,
  SUM(COALESCE(u.mrr_change, 0)) AS total_expansion_mrr,
  AVG(COALESCE(u.mrr_change, 0)) AS avg_expansion_per_user
FROM cohorts c
LEFT JOIN upgrades u ON c.user_id = u.user_id
GROUP BY c.cohort_month
ORDER BY c.cohort_month DESC;

Implementation Checklist

Phase 1: Signup & Activation (Week 1)

  • Signup Completed event
  • Define activation milestones (5-7 steps)
  • Track each milestone event
  • User Activated event
  • Onboarding email sequence
  • Profile attributes: activated, activation_date, activation_cohort

Phase 2: Feature Usage (Week 2)

  • Top 5 feature usage events
  • Premium Feature Gated events
  • Collaboration events (invited, joined, mentioned)
  • Usage-based quota tracking
  • Quota warning/exceeded triggers

Phase 3: Expansion & Retention (Week 3)

  • Plan Upgraded/Downgraded events
  • Seat Added event
  • Usage Dropped detection (cron job)
  • Cancellation Flow Started
  • At-risk account segment + outreach campaign

Phase 4: Analytics & Optimization (Week 4)

  • Activation rate dashboard (by source, plan)
  • Feature adoption analysis
  • Cohort retention + expansion
  • Churn reason analysis
  • Expansion-ready account identification

Next: Learn about communication channels and messaging → Communication Channels