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.
| Property | Type | Required | Description |
|---|---|---|---|
method | string | No | Registration method: 'email', 'google', 'sso' |
plan | string | No | Plan selected at sign-up, if any (e.g. 'starter', 'pro') |
invited_by | string | No | User ID of the person who sent the invitation |
referral_code | string | No | Referral code applied during sign-up |
_df.track('signed_up', {
method: 'google',
plan: 'pro',
referral_code: 'FRIEND2026'
})logged_in
A user has successfully authenticated.
| Property | Type | Required | Description |
|---|---|---|---|
method | string | No | Authentication 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.
| Property | Type | Required | Description |
|---|---|---|---|
plan_id | string | No | Internal identifier for the plan |
plan_name | string | No | Human-readable plan name (e.g. 'Pro Trial') |
trial_length_days | number | No | Number 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.
| Property | Type | Required | Description |
|---|---|---|---|
plan_id | string | No | Plan converted onto |
plan_name | string | No | Human-readable plan name |
revenue | number | No | First payment amount |
currency | string | No | ISO 4217 currency code (e.g. 'GBP', 'USD') |
payment_method | string | No | Payment 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.
| Property | Type | Required | Description |
|---|---|---|---|
plan_id | string | No | Plan that expired |
days_used | number | No | Number of trial days the user was active |
features_used | string[] | No | List 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.
| Property | Type | Required | Description |
|---|---|---|---|
plan_id | string | Yes | Plan identifier |
plan_name | string | No | Human-readable plan name |
revenue | number | No | Amount charged |
currency | string | No | ISO 4217 currency code |
billing_cycle | string | No | 'monthly' or 'annual' |
coupon | string | No | Coupon 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.
| Property | Type | Required | Description |
|---|---|---|---|
previous_plan_id | string | No | Plan being upgraded from |
new_plan_id | string | Yes | Plan being upgraded to |
revenue_delta | number | No | Additional revenue from the upgrade (prorated or full cycle) |
currency | string | No | ISO 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.
| Property | Type | Required | Description |
|---|---|---|---|
previous_plan_id | string | No | Plan being downgraded from |
new_plan_id | string | Yes | Plan being downgraded to |
reason | string | No | Reason 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.
| Property | Type | Required | Description |
|---|---|---|---|
plan_id | string | No | Plan being cancelled |
reason | string | No | Cancellation reason code (e.g. 'missing_feature', 'switching_product') |
feedback | string | No | Free-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.
| Property | Type | Required | Description |
|---|---|---|---|
plan_id | string | No | Plan that renewed |
revenue | number | No | Amount charged on renewal |
currency | string | No | ISO 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.
| Property | Type | Required | Description |
|---|---|---|---|
feature_id | string | Yes | Stable identifier for the feature (e.g. 'bulk_export') |
feature_name | string | No | Human-readable feature name |
context | string | No | Where 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.
| Property | Type | Required | Description |
|---|---|---|---|
report_id | string | No | Stable identifier for the report |
report_name | string | No | Display name (e.g. 'Monthly Revenue') |
filters | object | No | Active 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.
| Property | Type | Required | Description |
|---|---|---|---|
export_type | string | No | Format of the export: 'csv', 'pdf', 'xlsx' |
record_count | number | No | Number 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.
| Property | Type | Required | Description |
|---|---|---|---|
integration_id | string | Yes | Stable identifier for the integration |
integration_name | string | No | Display name (e.g. 'Salesforce') |
provider | string | No | Provider/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.
| Property | Type | Required | Description |
|---|---|---|---|
scope | string | No | Permission 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.
| Property | Type | Required | Description |
|---|---|---|---|
invitee_email_hash | string | No | SHA-256 hash of the invitee’s email address |
role | string | No | Role assigned to the invitee (e.g. 'admin', 'member', 'viewer') |
seats_total | number | No | Total 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.
| Property | Type | Required | Description |
|---|---|---|---|
inviter_id | string | No | User ID of the person who sent the invitation |
role | string | No | Role 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.
| Property | Type | Required | Description |
|---|---|---|---|
new_seat_count | number | No | Total seat count after the addition |
_df.track('seat_added', {
new_seat_count: 10
})seat_removed
A seat has been removed from the account.
| Property | Type | Required | Description |
|---|---|---|---|
new_seat_count | number | No | Total seat count after the removal |
reason | string | No | Reason for removal (e.g. 'user_offboarded', 'downgrade') |
_df.track('seat_removed', {
new_seat_count: 9,
reason: 'user_offboarded'
})