> ## 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.

# Lifecycle events

> The ten canonical events with typed SDK helpers.

Ten lifecycle events have first-class TypeScript helpers. Prefer them over `events.track()` — you get autocomplete and protection against name typos.

<Tip>
  The constant `STANDARD_EVENT_NAMES` is exported from `@vibefollow/sdk` if you need to enumerate the canonical set programmatically.
</Tip>

## The ten helpers

<CardGroup cols={3}>
  <Card title="signedUp" icon="user-plus" href="#signedup">
    `user_signed_up` — account created.
  </Card>

  <Card title="trialStarted" icon="hourglass-start" href="#trialstarted">
    `trial_started` — trial activated.
  </Card>

  <Card title="featureUsed" icon="sparkles" href="#featureused">
    `feature_used` — meaningful action in product.
  </Card>

  <Card title="onboardingStep" icon="list-check" href="#onboardingstep">
    `onboarding_step` — step started or completed.
  </Card>

  <Card title="subscriptionChanged" icon="arrow-up-right-dots" href="#subscriptionchanged">
    `subscription_changed` — any plan/interval change.
  </Card>

  <Card title="subscriptionCancelled" icon="ban" href="#subscriptioncancelled">
    `subscription_cancelled` — cancel requested, still has access.
  </Card>

  <Card title="paymentFailed" icon="credit-card" href="#paymentfailed">
    `payment_failed` — billing decline.
  </Card>

  <Card title="trialExpiring" icon="hourglass-end" href="#trialexpiring">
    `trial_expiring` — trial about to end.
  </Card>

  <Card title="userInvited" icon="user-group" href="#userinvited">
    `user_invited` — teammate invited.
  </Card>

  <Card title="usageThresholdReached" icon="gauge-high" href="#usagethresholdreached">
    `usage_threshold_reached` — nearing a plan cap.
  </Card>
</CardGroup>

## Reference table

| Helper                        | Event name                | Common properties                           |
| ----------------------------- | ------------------------- | ------------------------------------------- |
| `users.signedUp`              | `user_signed_up`          | `plan`, `source`                            |
| `users.trialStarted`          | `trial_started`           | `trialDays`                                 |
| `users.featureUsed`           | `feature_used`            | `feature` (required), `count`               |
| `users.onboardingStep`        | `onboarding_step`         | `step` (required), `completed`              |
| `users.subscriptionChanged`   | `subscription_changed`    | `from`, `to` (required), `interval`, `mrr`  |
| `users.subscriptionCancelled` | `subscription_cancelled`  | `reason`, `effectiveAt`                     |
| `users.paymentFailed`         | `payment_failed`          | `reason`                                    |
| `users.trialExpiring`         | `trial_expiring`          | `trialDays` (required)                      |
| `users.userInvited`           | `user_invited`            | `invitedEmail` (required)                   |
| `users.usageThresholdReached` | `usage_threshold_reached` | `usagePercent` (required), `meter`, `limit` |

## `signedUp`

Fires when a user creates their account. Drives the `signup` audience and the "welcome" trigger rule.

```ts theme={null}
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.

```ts theme={null}
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!`).

```ts theme={null}
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.

```ts theme={null}
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`).

```ts theme={null}
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).

```ts theme={null}
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.

```ts theme={null}
await vf.users.paymentFailed('usr_42', { reason: 'card_declined' });
```

## `trialExpiring`

<Note>
  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`](#trialstarted) with `{ trialDays }` once and the rest is handled.
</Note>

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.

```ts theme={null}
await vf.users.trialExpiring('usr_42', { trialDays: 3 });
```

## `userInvited`

Fires when a user invites a teammate. Powers the team-expansion nudges.

```ts theme={null}
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.

```ts theme={null}
await vf.users.usageThresholdReached('usr_42', {
  usagePercent: 85,
  meter: 'seats',
  limit: 10,
});
```

## What if my event isn't on this list?

<Note>
  Use `events.track()`. Custom event names are accepted as-is and become first-class citizens in audience filters. See [Custom events](/events/custom-events).
</Note>
