Event SpecsSaaS / B2B

SaaS / B2B Event Spec

This page defines the recommended event taxonomy for SaaS and B2B software products. The events cover the full customer lifecycle — from first sign-up through trial, subscription, product usage, and team collaboration.

All event names use snake_case. Property names also use snake_case. Keep event names consistent across your codebase to ensure reliable pipeline transformations and clean data warehouse output.


Authentication

Events relating to user sign-up, sign-in, and account access.

signed_up

A new user has created an account.

PropertyTypeRequiredDescription
methodstringNoRegistration method: 'email', 'google', 'sso'
planstringNoPlan selected at sign-up, if any (e.g. 'starter', 'pro')
invited_bystringNoUser ID of the person who sent the invitation
referral_codestringNoReferral code applied during sign-up
_df.track('signed_up', {
  method: 'google',
  plan: 'pro',
  referral_code: 'FRIEND2026'
})

logged_in

A user has successfully authenticated.

PropertyTypeRequiredDescription
methodstringNoAuthentication method (e.g. 'email', 'sso', 'magic_link')
_df.track('logged_in', {
  method: 'sso'
})

logged_out

A user has ended their session.

No additional properties.

_df.track('logged_out')

password_reset_requested

A user has requested a password reset email.

No additional properties.

_df.track('password_reset_requested')

Trial & Subscription

Events relating to trial activation, conversion, and subscription lifecycle.

trial_started

A user has entered a free trial period.

PropertyTypeRequiredDescription
plan_idstringNoInternal identifier for the plan
plan_namestringNoHuman-readable plan name (e.g. 'Pro Trial')
trial_length_daysnumberNoNumber of days in the trial
_df.track('trial_started', {
  plan_id: 'plan_pro',
  plan_name: 'Pro',
  trial_length_days: 14
})

trial_converted

A trial user has upgraded to a paid subscription.

PropertyTypeRequiredDescription
plan_idstringNoPlan converted onto
plan_namestringNoHuman-readable plan name
revenuenumberNoFirst payment amount
currencystringNoISO 4217 currency code (e.g. 'GBP', 'USD')
payment_methodstringNoPayment method used (e.g. 'card', 'invoice')
_df.track('trial_converted', {
  plan_id: 'plan_pro_monthly',
  plan_name: 'Pro Monthly',
  revenue: 49.00,
  currency: 'GBP',
  payment_method: 'card'
})

trial_expired

A trial period ended without conversion.

PropertyTypeRequiredDescription
plan_idstringNoPlan that expired
days_usednumberNoNumber of trial days the user was active
features_usedstring[]NoList of feature IDs used during the trial
_df.track('trial_expired', {
  plan_id: 'plan_pro',
  days_used: 9,
  features_used: ['reporting', 'integrations']
})

subscription_started

A new paid subscription has been activated.

PropertyTypeRequiredDescription
plan_idstringYesPlan identifier
plan_namestringNoHuman-readable plan name
revenuenumberNoAmount charged
currencystringNoISO 4217 currency code
billing_cyclestringNo'monthly' or 'annual'
couponstringNoCoupon or promo code applied
_df.track('subscription_started', {
  plan_id: 'plan_pro_annual',
  plan_name: 'Pro Annual',
  revenue: 468.00,
  currency: 'GBP',
  billing_cycle: 'annual',
  coupon: 'LAUNCH20'
})

subscription_upgraded

A subscriber has moved to a higher-tier plan.

PropertyTypeRequiredDescription
previous_plan_idstringNoPlan being upgraded from
new_plan_idstringYesPlan being upgraded to
revenue_deltanumberNoAdditional revenue from the upgrade (prorated or full cycle)
currencystringNoISO 4217 currency code
_df.track('subscription_upgraded', {
  previous_plan_id: 'plan_starter_monthly',
  new_plan_id: 'plan_pro_monthly',
  revenue_delta: 30.00,
  currency: 'GBP'
})

subscription_downgraded

A subscriber has moved to a lower-tier plan.

PropertyTypeRequiredDescription
previous_plan_idstringNoPlan being downgraded from
new_plan_idstringYesPlan being downgraded to
reasonstringNoReason code or label provided by the user
_df.track('subscription_downgraded', {
  previous_plan_id: 'plan_pro_monthly',
  new_plan_id: 'plan_starter_monthly',
  reason: 'too_expensive'
})

subscription_cancelled

A subscription has been cancelled.

PropertyTypeRequiredDescription
plan_idstringNoPlan being cancelled
reasonstringNoCancellation reason code (e.g. 'missing_feature', 'switching_product')
feedbackstringNoFree-text feedback from the user
⚠️

Do not send raw email addresses in feedback. If your cancellation flow captures open text, sanitise or omit it before sending.

_df.track('subscription_cancelled', {
  plan_id: 'plan_pro_monthly',
  reason: 'switching_product',
  feedback: 'Moving to a tool with better API support'
})

subscription_renewed

A subscription billing cycle has renewed successfully.

PropertyTypeRequiredDescription
plan_idstringNoPlan that renewed
revenuenumberNoAmount charged on renewal
currencystringNoISO 4217 currency code
_df.track('subscription_renewed', {
  plan_id: 'plan_pro_annual',
  revenue: 468.00,
  currency: 'GBP'
})

Product Usage

Events tracking in-product engagement and feature adoption.

feature_used

A user has interacted with a specific product feature.

PropertyTypeRequiredDescription
feature_idstringYesStable identifier for the feature (e.g. 'bulk_export')
feature_namestringNoHuman-readable feature name
contextstringNoWhere in the UI the feature was used (e.g. 'dashboard', 'settings')
_df.track('feature_used', {
  feature_id: 'bulk_export',
  feature_name: 'Bulk Export',
  context: 'reports'
})

report_viewed

A user has opened or refreshed a report.

PropertyTypeRequiredDescription
report_idstringNoStable identifier for the report
report_namestringNoDisplay name (e.g. 'Monthly Revenue')
filtersobjectNoActive filter state at time of view
_df.track('report_viewed', {
  report_id: 'rpt_monthly_revenue',
  report_name: 'Monthly Revenue',
  filters: { date_range: 'last_30_days', segment: 'pro_users' }
})

export_completed

A data export has finished successfully.

PropertyTypeRequiredDescription
export_typestringNoFormat of the export: 'csv', 'pdf', 'xlsx'
record_countnumberNoNumber of records included in the export
_df.track('export_completed', {
  export_type: 'csv',
  record_count: 1450
})

integration_connected

A user has successfully connected a third-party integration.

PropertyTypeRequiredDescription
integration_idstringYesStable identifier for the integration
integration_namestringNoDisplay name (e.g. 'Salesforce')
providerstringNoProvider/vendor name
_df.track('integration_connected', {
  integration_id: 'int_salesforce',
  integration_name: 'Salesforce',
  provider: 'salesforce'
})

api_key_created

A user has generated a new API key.

PropertyTypeRequiredDescription
scopestringNoPermission scope of the key (e.g. 'read_only', 'full_access')
_df.track('api_key_created', {
  scope: 'read_only'
})

Collaboration & Team

Events relating to team invitations, seat management, and multi-user activity.

invitation_sent

A team member has been invited to the workspace.

PropertyTypeRequiredDescription
invitee_email_hashstringNoSHA-256 hash of the invitee’s email address
rolestringNoRole assigned to the invitee (e.g. 'admin', 'member', 'viewer')
seats_totalnumberNoTotal seat count on the account after sending the invitation
⚠️

Never send raw email addresses as event properties. Hash using SHA-256 before tracking.

_df.track('invitation_sent', {
  invitee_email_hash: 'b3a8e0e1...', // SHA-256 of invitee email
  role: 'member',
  seats_total: 8
})

invitation_accepted

An invited user has accepted their invitation and joined the workspace.

PropertyTypeRequiredDescription
inviter_idstringNoUser ID of the person who sent the invitation
rolestringNoRole the new user was assigned
_df.track('invitation_accepted', {
  inviter_id: 'user_123',
  role: 'member'
})

seat_added

A new seat has been added to the account.

PropertyTypeRequiredDescription
new_seat_countnumberNoTotal seat count after the addition
_df.track('seat_added', {
  new_seat_count: 10
})

seat_removed

A seat has been removed from the account.

PropertyTypeRequiredDescription
new_seat_countnumberNoTotal seat count after the removal
reasonstringNoReason for removal (e.g. 'user_offboarded', 'downgrade')
_df.track('seat_removed', {
  new_seat_count: 9,
  reason: 'user_offboarded'
})