Skip to main content
Ten lifecycle events have first-class TypeScript helpers. Prefer them over events.track() — you get autocomplete and protection against name typos.
The constant STANDARD_EVENT_NAMES is exported from @vibefollow/sdk if you need to enumerate the canonical set programmatically.

The ten helpers

signedUp

user_signed_up — account created.

trialStarted

trial_started — trial activated.

featureUsed

feature_used — meaningful action in product.

onboardingStep

onboarding_step — step started or completed.

subscriptionChanged

subscription_changed — any plan/interval change.

subscriptionCancelled

subscription_cancelled — cancel requested, still has access.

paymentFailed

payment_failed — billing decline.

trialExpiring

trial_expiring — trial about to end.

userInvited

user_invited — teammate invited.

usageThresholdReached

usage_threshold_reached — nearing a plan cap.

Reference table

HelperEvent nameCommon properties
users.signedUpuser_signed_upplan, source
users.trialStartedtrial_startedtrialDays
users.featureUsedfeature_usedfeature (required), count
users.onboardingSteponboarding_stepstep (required), completed
users.subscriptionChangedsubscription_changedfrom, to (required), interval, mrr
users.subscriptionCancelledsubscription_cancelledreason, effectiveAt
users.paymentFailedpayment_failedreason
users.trialExpiringtrial_expiringtrialDays (required)
users.userInviteduser_invitedinvitedEmail (required)
users.usageThresholdReachedusage_threshold_reachedusagePercent (required), meter, limit

signedUp

Fires when a user creates their account. Drives the signup audience and the “welcome” trigger rule.
await vf.users.signedUp('usr_42', {
  plan: 'trial',
  source: 'organic',
});

trialStarted

Fires when a trial activates. Often the same moment as signup; emit both if so. trialDays is the integer length of the trial in days — Vibefollow uses it to derive the trial-end date, which powers the trial_expiring trigger automatically. Send it once and you don’t need to track the expiry yourself.
await vf.users.trialStarted('usr_42', { trialDays: 14 });

featureUsed

Fires when the user does something interesting in your product. The feature string is the audience key — pick stable identifiers (dashboard_export, not Dashboard Export!).
await vf.users.featureUsed('usr_42', {
  feature: 'dashboard_export',
  count: 1,
});

onboardingStep

Fires when a user completes (or starts) an onboarding step. Powers the “onboarding stalled” trigger that nudges users who got partway through.
await vf.users.onboardingStep('usr_42', {
  step: 'connect_integration',
  completed: true,
});

subscriptionChanged

Fires on any plan change — trial → pro, pro → enterprise, monthly → yearly. The interval field is denormalised onto the user row so audience filters like “all yearly subscribers” can be answered without scanning event JSON. mrr should be in cents (9900 = $99/mo).
await vf.users.subscriptionChanged('usr_42', {
  from: 'trial',
  to: 'pro',
  interval: 'yearly',
  mrr: 9900,
});

subscriptionCancelled

Fires when the user clicks Cancel but still has access. Distinct from churned (truly gone after the grace period — emit a follow-up subscription_changed with to: 'churned' when access actually ends).
await vf.users.subscriptionCancelled('usr_42', {
  reason: 'too_expensive',
  effectiveAt: '2026-06-01T00:00:00Z',
});

paymentFailed

Fires on any billing decline. Powers the “card declined” recovery flow.
await vf.users.paymentFailed('usr_42', { reason: 'card_declined' });

trialExpiring

You usually don’t need this. The trial_expiring trigger is derived automatically — Vibefollow reads trialDays from the most recent trial_started event and fires when the trial is within ~48h of ending. Just call trialStarted with { trialDays } once and the rest is handled.
Emit this only if your trial length is dynamic (e.g. extended by a sales rep) and you want to re-assert the remaining window. trialDays is the integer number of days left.
await vf.users.trialExpiring('usr_42', { trialDays: 3 });

userInvited

Fires when a user invites a teammate. Powers the team-expansion nudges.
await vf.users.userInvited('usr_42', {
  invitedEmail: 'colleague@acme.io',
});

usageThresholdReached

Fires when a paying user crosses a usage threshold against a metered plan limit (default ~80%). Drives the plan_limit_upsell trigger so the AI can pitch an upgrade before the user hits the cap. meter is an optional free-form label (seats, api_calls, …) so audience rules can target a specific meter; limit is the absolute cap if known.
await vf.users.usageThresholdReached('usr_42', {
  usagePercent: 85,
  meter: 'seats',
  limit: 10,
});

What if my event isn’t on this list?

Use events.track(). Custom event names are accepted as-is and become first-class citizens in audience filters. See Custom events.