Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vibefollow.com/llms.txt

Use this file to discover all available pages before exploring further.

The vf.users resource handles user upsert and lifecycle event emission. It wraps POST /api/v1/users (for identify) and POST /api/v1/events (for every helper).

identify(userId, traits)

Upserts a user. Same call works for first-touch and updates — the backend deduplicates by external_user_id.
await vf.users.identify('usr_42', {
  email: 'jane@acme.io',
  name: 'Jane Doe',
  plan: 'pro',
  signupDate: '2026-05-17T14:00:00Z',
  company: 'Acme Inc.',
  role: 'engineering_lead',
});
userId
string
required
Your primary key for the user. Used as external_user_id server-side. Stable across re-identifies.
traits
IdentifyTraits
required
Trait bag. The known fields email, name, plan, signupDate, company, role are stored in dedicated columns; everything else flows into the traits JSON column.
Returns: Promise<void>. Throws on validation / network / auth failure — see Errors.

Lifecycle helpers

All nine return Promise<void> and emit the canonical event name from STANDARD_EVENT_NAMES. Properties are optional unless flagged required.

signedUp(userId, properties?)

Emits user_signed_up.
await vf.users.signedUp('usr_42', {
  plan: 'trial',
  source: 'organic',
});
userId
string
required
External user ID.
properties.plan
string
Plan the user signed up on (trial, pro, enterprise).
properties.source
string
Marketing source (organic, referral, ads, …).

trialStarted(userId, properties?)

Emits trial_started.
await vf.users.trialStarted('usr_42', { trialDays: 14 });
properties.trialDays
number
Duration of the trial in days.

featureUsed(userId, properties)

Emits feature_used. feature is required.
await vf.users.featureUsed('usr_42', {
  feature: 'dashboard_export',
  count: 1,
});
properties.feature
string
required
Stable feature identifier (snake_case recommended).
properties.count
number
Times used in this interaction. Default 1 server-side.

onboardingStep(userId, properties)

Emits onboarding_step. step is required.
await vf.users.onboardingStep('usr_42', {
  step: 'connect_integration',
  completed: true,
});
properties.step
string
required
Onboarding step identifier.
properties.completed
boolean
Whether this step was just completed (true) or merely started (false).

subscriptionChanged(userId, properties)

Emits subscription_changed. to is required. interval is denormalised onto the user row for audience filters.
await vf.users.subscriptionChanged('usr_42', {
  from: 'trial',
  to: 'pro',
  interval: 'yearly',
  mrr: 9900,
});
properties.from
string
Previous plan.
properties.to
string
required
New plan.
properties.interval
'monthly' | 'yearly' | 'lifetime'
Billing interval.
properties.mrr
number
Monthly recurring revenue, in cents. 9900 = $99/mo.

subscriptionCancelled(userId, properties?)

Emits subscription_cancelled. Flips the user to the cancelled lifecycle stage (active-but-departing). Distinct from churned (truly gone after grace period).
await vf.users.subscriptionCancelled('usr_42', {
  reason: 'too_expensive',
  effectiveAt: '2026-06-01T00:00:00Z',
});
properties.reason
string
Free-text cancellation reason.
properties.effectiveAt
string
ISO 8601 timestamp at which access actually ends. Defaults to “now” server-side if omitted.

paymentFailed(userId, properties?)

Emits payment_failed.
await vf.users.paymentFailed('usr_42', { reason: 'card_declined' });
properties.reason
string
Decline reason from your billing system (card_declined, insufficient_funds, …).

trialExpiring(userId, properties)

Emits trial_expiring. daysLeft is required.
await vf.users.trialExpiring('usr_42', { daysLeft: 3 });
properties.daysLeft
number
required
Days remaining in the trial. Your billing system controls the cadence.

userInvited(userId, properties)

Emits user_invited. invitedEmail is required.
await vf.users.userInvited('usr_42', {
  invitedEmail: 'colleague@acme.io',
});
properties.invitedEmail
string
required
Email address that received the invitation.

Why typed helpers vs events.track()

You can emit the same nine events through vf.events.track('user_signed_up', …). You shouldn’t, because:

Typo protection

signedUp autocompletes; siignedUp is a type error.

Property shapes

Validated by TypeScript at the call site, not just server-side.

Discoverable

vf.users.<dot> lists the canonical set in IDE autocomplete.
For anything outside the canonical 9, fall back to events.track().