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

# Quickstart

> Identify a user and emit your first event in under a minute.

<Steps>
  <Step title="Install the SDK">
    Node 20 or later is required. The SDK also runs on Cloudflare Workers (with `nodejs_compat`), Vercel Edge, and Deno. See [Edge runtimes](/sdk/installation#edge-runtimes) for the runtime-specific notes.

    <Tabs>
      <Tab title="npm">
        ```bash theme={null}
        npm install @vibefollow/sdk
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash theme={null}
        pnpm add @vibefollow/sdk
        ```
      </Tab>

      <Tab title="yarn">
        ```bash theme={null}
        yarn add @vibefollow/sdk
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Grab an API key">
    API keys are issued in the Vibefollow dashboard under **Settings → Developers → API keys**. They follow the Stripe-style prefix scheme:

    * `sk_live_…` — production
    * `sk_test_…` — sandbox (future)

    Store the key in your secrets manager and expose it to your process as `VIBEFOLLOW_API_KEY`.

    <Warning>
      Never commit a key, and never ship one to the browser. The full secret is shown exactly once at creation time — if you lose it, revoke and reissue.
    </Warning>
  </Step>

  <Step title="Identify a user and emit an event">
    ```ts theme={null}
    import { VibeFollow } from '@vibefollow/sdk';

    const vf = new VibeFollow({ apiKey: process.env.VIBEFOLLOW_API_KEY! });

    // Upsert a user
    await vf.users.identify('usr_42', {
      email: 'jane@acme.io',
      name: 'Jane Doe',
      plan: 'trial',
    });

    // Emit a lifecycle event
    await vf.users.signedUp('usr_42', { plan: 'trial', source: 'organic' });
    ```

    That's it. The SDK takes care of authentication, idempotency, retries, and error mapping.

    <Note>
      `identify` is an upsert — calling it twice with the same `userId` updates traits in place. Pass any trait keys you want (`plan`, `signupDate`, `company`, `role`, …); unknown keys flow into the `traits` JSON column for audience filtering.
    </Note>
  </Step>

  <Step title="Try a real lifecycle event">
    The ten canonical lifecycle events have typed helpers. They're equivalent to calling `events.track('user_signed_up', …)` but you get autocomplete and protection against name typos.

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

    await vf.users.featureUsed('usr_42', {
      feature: 'dashboard_export',
      count: 1,
    });

    await vf.users.subscriptionChanged('usr_42', {
      from: 'trial',
      to: 'pro',
      interval: 'yearly',
      mrr: 9900, // cents
    });
    ```

    See the full list in [Lifecycle events](/events/lifecycle-events).
  </Step>

  <Step title="Verify a webhook (optional)">
    Vibefollow signs every outbound webhook delivery. Verify it with `webhooks.constructEvent()` — pass the raw body, the signature header, and your webhook secret:

    ```ts theme={null}
    import { VibeFollow, WebhookSignatureError } from '@vibefollow/sdk';

    const vf = new VibeFollow({ apiKey: process.env.VIBEFOLLOW_API_KEY! });

    app.post('/webhooks/vibefollow', express.raw({ type: '*/*' }), (req, res) => {
      try {
        const event = vf.webhooks.constructEvent(
          req.body, // raw Buffer or string &mdash; not JSON.parse'd
          req.headers['x-vibefollow-signature'],
          process.env.VIBEFOLLOW_WEBHOOK_SECRET!,
        );

        if (event.type === 'email.replied') {
          console.log('reply tone:', event.data.tone);
        }

        res.sendStatus(204);
      } catch (err) {
        if (err instanceof WebhookSignatureError) return res.sendStatus(401);
        throw err;
      }
    });
    ```

    Full payload spec for every event type: [Webhook event types](/webhooks/event-types).
  </Step>
</Steps>

<Check>
  Your first event is now flowing. Calls to `identify` and lifecycle helpers return `Promise<void>` — if they resolve without throwing, the event was accepted (`202 Accepted`) and is on the ingest queue.
</Check>

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/authentication">
    Issue, store, rotate, and revoke API keys the right way.
  </Card>

  <Card title="Events overview" icon="bolt" href="/events/overview">
    The mental model behind identify + track, and how events drive triggers.
  </Card>

  <Card title="The 10 lifecycle events" icon="list" href="/events/lifecycle-events">
    First-class helpers for every canonical event Vibefollow knows about.
  </Card>

  <Card title="Custom events" icon="code-branch" href="/events/custom-events">
    `events.track()` for anything outside the canonical set, plus batching for backfills.
  </Card>

  <Card title="Import from Stripe" icon="stripe" href="/integrations/stripe-import">
    Backfill existing customers and billing history with a read-only restricted key — no code.
  </Card>

  <Card title="Webhooks overview" icon="webhook" href="/webhooks/overview">
    Signature verification, retry policy, and the inbound event catalog.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/sdk/errors">
    Typed errors you can `catch` — `AuthError`, `RateLimitError`, etc.
  </Card>
</CardGroup>
