Use when integrating Outlit tracking into web, server, native, or desktop apps; adding SDK event tracking, identity, consent, activation, billing, visitor tr...
---
name: outlit-sdk
description: Use when integrating Outlit tracking into web, server, native, or desktop apps; adding SDK event tracking, identity, consent, activation, billing, visitor tracking, customerId attribution, or troubleshooting @outlit/browser, @outlit/node, or the Rust outlit crate.
---
# Outlit SDK Integration
Decision-tree guide for adding product and website tracking to the Outlit customer context graph. Detect first, ask only when needed, keep changes small, and prefer official docs for framework-specific code.
## Branching Check
Before anything else, check whether Outlit is already installed:
1. Look for `@outlit/browser`, `@outlit/node`, `outlit` in `Cargo.toml`, `https://cdn.outlit.ai`, or existing `outlit.init(...)` / `new Outlit(...)` calls.
2. If not installed, go to [Phase 1: Quick Connect](#phase-1-quick-connect).
3. If installed, go to [Already Installed](#already-installed).
## Already Installed
Ask what they need help with, then run detection before changing code:
- Add event tracking -> [Decision 6: Event Tracking](#decision-6-event-tracking)
- Add/change auth identity -> [Decision 2: Identity](#decision-2-identity)
- Add consent management -> [Decision 1: Consent](#decision-1-consent)
- Add server/native tracking -> [Decision 7: Server and Native Tracking](#decision-7-server-and-native-tracking)
- Add activation tracking -> [Decision 4: Activation](#decision-4-activation)
- Add billing/Stripe integration -> [Decision 5: Billing](#decision-5-billing)
- Migrate from other analytics -> [Decision 3: Existing Analytics](#decision-3-existing-analytics)
- Debug/troubleshoot -> [Troubleshooting](#troubleshooting)
## Current API Guardrails
These points prevent the most common stale integrations:
- All current ingest SDKs use `publicKey` values that start with `pk_`. Do not use `privateKey`, `OUTLIT_PRIVATE_KEY`, `visitorId`, or `event` for `@outlit/node` examples.
- Node server events use `new Outlit({ publicKey })` and `track({ eventName, email | userId | fingerprint | customerId, properties })`.
- Browser events use `track(eventName, properties?)`; browser anonymous events are valid without identity because the SDK manages `visitorId`.
- Server `track()` requires at least one of `email`, `userId`, `fingerprint`, or `customerId`.
- Server `identify()` requires `email` or `userId`. `customerId` is optional account/workspace attribution, not a user identity by itself.
- Use `customerId` for your system-owned account/workspace/customer ID. The TypeScript SDK no longer uses `customerDomain`; do not add it.
- Browser `setUser()` can be called before tracking is enabled and will be applied after consent. Browser `identify()` requires tracking to already be enabled.
- `user.activate()` is the only journey stage new integrations should send manually. `user.engaged()` and `user.inactive()` are deprecated; Outlit derives engagement and inactivity from tracked product activity.
- Billing is account-level. In TypeScript billing methods, prefer `customerId`; `stripeCustomerId` is still accepted for Stripe-backed billing identifiers. In Rust billing methods still start with a domain and can add `.customer_id(...)`.
- Event names should be `snake_case`.
## Phase 1: Quick Connect
Goal: get data flowing quickly so the user can see Outlit connection/activity without making product-strategy decisions.
### Step 1: Detect Framework and Package Manager
Check:
| Signal | How to detect |
|--------|---------------|
| Framework | `package.json` deps: `next`, `react`, `vue`, `nuxt`, `svelte`, `@sveltejs/kit`, `@angular/core`, `astro`, `express`, `fastify`, `electron`, `react-native`; `Cargo.toml` deps: `tauri`, `outlit` |
| Package manager | `bun.lockb`, `bun.lock`, `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json` |
| Current Outlit | `@outlit/browser`, `@outlit/node`, CDN script, Rust crate, `outlit.init`, `OutlitProvider`, `OutlitPlugin`, `new Outlit` |
### Step 2: Choose SDK
| App surface | Use |
|-------------|-----|
| Browser app: React, Next.js, Vue, Nuxt, SvelteKit, Angular, Astro, vanilla, script tag | `@outlit/browser` |
| Node backend: API routes, server actions, Express, Fastify, jobs, webhooks | `@outlit/node` |
| Native JS without browser storage: React Native, CLI, desktop main process | `@outlit/node` with a stable `fingerprint` |
| Rust backend, CLI, or Tauri backend | Rust crate `outlit` |
| Electron renderer or webview | `@outlit/browser`; use `@outlit/node` in main process only if tracking native/background events |
Install with the detected manager. Prefer `bun add` when the repo is a Bun workspace.
### Step 3: Add Public Key
Ask for the Outlit public key from **Outlit dashboard -> Settings -> Website Tracking** or onboarding.
Use the framework's public env convention:
| Framework | Env var |
|-----------|---------|
| Next.js | `NEXT_PUBLIC_OUTLIT_KEY` |
| Vite / Vue / React+Vite / Svelte | `VITE_OUTLIT_KEY` |
| SvelteKit | `PUBLIC_OUTLIT_KEY` |
| Nuxt | `NUXT_PUBLIC_OUTLIT_KEY` |
| Astro | `PUBLIC_OUTLIT_KEY` |
| Create React App | `REACT_APP_OUTLIT_KEY` |
| Angular | `environment.ts` or equivalent |
| Server/native | `OUTLIT_KEY` or `OUTLIT_PUBLIC_KEY` |
### Step 4: Minimal Setup
Fetch the framework doc from the [Doc URL Map](#doc-url-map) and implement only startup initialization:
- React/Next: prefer `OutlitProvider` from `@outlit/browser/react` in a client boundary.
- Vue/Nuxt: use `OutlitPlugin` from `@outlit/browser/vue`.
- Plain browser/script: use `@outlit/browser` or CDN script.
- Server/native: create one `Outlit` instance where the process can reuse it.
Do not add custom events, activation, billing, auth, or consent until basic tracking is working unless the user asked for a full integration immediately.
### Step 5: Verify Connection
Verify with one or more:
- DevTools Network has `https://app.outlit.ai/api/i/v1/<publicKey>/events` returning success.
- Browser console has no `[Outlit]` warnings.
- Server code calls `await outlit.flush()` or `await outlit.shutdown()` before process/serverless exit.
- Outlit onboarding/dashboard shows recent activity or connected tracking.
Then ask whether to continue with the full integration.
## Phase 2: Full Integration
Run detection and present what you found before changing behavior:
| Signal | How to detect |
|--------|---------------|
| Auth provider | `@clerk/*`, `next-auth`, `@auth/core`, `@supabase/*`, `@auth0/*`, `firebase`, custom session/auth files |
| Account model | `organization`, `workspace`, `team`, `account`, `tenant`, `customerId`, Stripe customer mapping |
| Billing provider | `stripe`, `@stripe/stripe-js`, `paddle`, `chargebee`, webhook routes |
| Existing analytics | `posthog-js`, `@posthog/node`, Amplitude, Mixpanel, Segment, analytics wrappers |
| Consent | Cookiebot, OneTrust, cookie banner components, CMP callbacks |
| Native/device | Tauri, Electron main process, React Native, CLI, mobile storage |
| Activation | first workspace/project/resource, first successful integration, invite sent, report generated, first meaningful feature success |
| Calendar embeds | Cal.com or Calendly embed code |
## Decision 1: Consent
| Detection | Recommendation |
|-----------|----------------|
| Existing CMP/cookie banner | Initialize with `autoTrack: false`; call `enableTracking()` from the CMP accept callback and `disableTracking()` on revoke/decline |
| EU/privacy signals but no CMP | Use `autoTrack: false` and tell the user they need a consent decision; do not build a CMP unless asked |
| No consent requirement found | Use default `autoTrack: true` |
Explain the tradeoff: `autoTrack: true` creates browser visitor storage immediately. `autoTrack: false` waits until `enableTracking()` is called.
If identity is known before consent, use the React `user` prop or `setUser()` so identity is queued and applied after tracking starts. Do not call browser `identify()` before tracking is enabled.
## Decision 2: Identity
Use the strongest identifiers available:
- `email`: primary person identifier.
- `userId`: the app/auth-provider user or contact ID.
- `customerId`: the app's account/workspace/customer/team ID.
- `customerTraits`: account/workspace metadata such as plan or seats.
- `traits`: user/contact metadata such as name or role.
- `fingerprint`: stable device/install ID for native or non-browser tracking.
Recommendations:
| Context | Pattern |
|---------|---------|
| React/Next | Pass `user={{ email, userId, customerId, traits, customerTraits }}` to `OutlitProvider` once auth resolves |
| Vue/Nuxt | Use `useOutlitUser(refOrComputedUser)` or plugin `setUser()` |
| SPA without framework helpers | Call `setUser({ email, userId, customerId })` after auth, and `clearUser()` on logout |
| Script tag | Call `window.outlit.setUser(...)` after auth, or `identify(...)` after tracking is enabled |
| Server Node | Call `identify({ email, userId, customerId })` for user/profile updates; call `track({ customerId, eventName })` for account-scoped events |
| Native/device | Track with a persistent `fingerprint`; later call `identify({ email, fingerprint })` or Rust `.fingerprint(...)` to link history |
Critical distinction: browser identify/setUser links anonymous visitor history. Server identify attributes server-side identity and can link fingerprints/customer IDs, but it does not link browser visitor cookies unless the browser also identifies.
## Decision 3: Existing Analytics
| Detection | Recommendation |
|-----------|----------------|
| Existing analytics wrapper | Add Outlit to the wrapper |
| Direct analytics calls scattered across files | Count them and ask whether to add Outlit alongside existing calls or introduce a wrapper |
| PostHog connected as data source | Do not duplicate every PostHog event by default; use SDK for missing product/website/customer identity gaps |
| No analytics | Add Outlit directly |
Keep event names `snake_case` and avoid reorganizing unrelated analytics code.
## Decision 4: Activation
Activation means a user reached the product's meaningful value moment. It is not automatically "completed onboarding" unless onboarding itself delivers value.
1. Scan for first-value actions: first project/resource created, first integration connected, first report/export generated, first invite, first successful core workflow.
2. Suggest the strongest candidate and ask for confirmation if ambiguous.
3. Call `user.activate()` only after the action is confirmed complete, usually after the backend succeeds.
4. Ensure the user is identified first. Browser `user.activate()` can queue until identity is set, but the cleaner integration is to set identity before activation.
5. Do not call `user.engaged()` or `user.inactive()` in new integrations.
## Decision 5: Billing
Billing is account-level context, separate from contact journey stages.
| Detection | Recommendation |
|-----------|----------------|
| Stripe connected in Outlit | Let the Stripe integration handle trialing/paid/churned |
| Stripe in app but not connected | Recommend connecting Stripe in Outlit; only add manual SDK calls if they need custom logic |
| Other/custom billing | Add billing calls in existing webhook/job handlers |
| No billing | Skip |
TypeScript:
```ts
outlit.customer.paid({
customerId: "cust_123",
properties: { plan: "pro" },
})
```
Rust currently differs:
```rust
client.customer()
.paid("acme.com")
.customer_id("cust_123")
.send()
.await?;
```
Always flush server/native queues before the handler exits.
## Decision 6: Event Tracking
Browser SDK defaults already capture:
- pageviews, including SPA navigation
- form submissions with sensitive fields removed
- auto-identify from email/name form fields
- engagement time and sessions
- Cal.com/Calendly booking events, without attendee email/name from client-side embeds
Add custom `track()` calls only for meaningful product actions that are not covered by automatic capture or connected integrations. Use properties for useful context, not PII or secrets.
Browser example:
```ts
outlit.track("project_created", { template: "blank" })
```
Server example:
```ts
outlit.track({
customerId: "cust_123",
eventName: "workspace_synced",
properties: { provider: "github" },
})
```
Calendar embed limitation: Cal.com and Calendly client events do not expose attendee email/name. Use provider webhooks plus server `identify()` when booked-meeting identity matters.
Hash-routed SPAs, `file://` routes, and Electron-style hash paths are handled by the core path extractor; do not add custom path parsing unless the app has a special router.
## Decision 7: Server and Native Tracking
Use server/native tracking for backend-confirmed product activity, billing, background jobs, native apps, CLIs, and device-based activity.
Node example:
```ts
import { Outlit } from "@outlit/node"
const outlit = new Outlit({ publicKey: process.env.OUTLIT_KEY! })
outlit.track({
customerId: "cust_123",
eventName: "workspace_synced",
properties: { provider: "github" },
})
await outlit.flush()
```
Use `fingerprint` for native/desktop/mobile activity before login:
```ts
outlit.track({
fingerprint: deviceId,
eventName: "app_opened",
})
outlit.identify({
email: user.email,
fingerprint: deviceId,
userId: user.id,
})
```
Serverless rule: `await outlit.flush()` before returning. Long-lived process rule: reuse one client and call `shutdown()` on graceful shutdown.
## Doc URL Map
Fetch docs as needed. Prefer docs over hardcoding long framework patterns, but apply the API guardrails above if examples conflict.
| Topic | URL |
|-------|-----|
| Quickstart | `https://docs.outlit.ai/tracking/quickstart` |
| How tracking works | `https://docs.outlit.ai/tracking/how-it-works` |
| Customer context graph | `https://docs.outlit.ai/concepts/customer-context-graph` |
| Website visitors | `https://docs.outlit.ai/concepts/website-visitors` |
| Identity resolution | `https://docs.outlit.ai/concepts/identity-resolution` |
| Customer journey | `https://docs.outlit.ai/concepts/customer-journey` |
| NPM/browser package | `https://docs.outlit.ai/tracking/browser/npm` |
| React | `https://docs.outlit.ai/tracking/browser/react` |
| Next.js | `https://docs.outlit.ai/tracking/browser/nextjs` |
| Vue 3 | `https://docs.outlit.ai/tracking/browser/vue` |
| Nuxt | `https://docs.outlit.ai/tracking/browser/nuxt` |
| SvelteKit | `https://docs.outlit.ai/tracking/browser/sveltekit` |
| Angular | `https://docs.outlit.ai/tracking/browser/angular` |
| Astro | `https://docs.outlit.ai/tracking/browser/astro` |
| Script tag | `https://docs.outlit.ai/tracking/browser/script` |
| Calendar embeds | `https://docs.outlit.ai/tracking/browser/calendar-embeds` |
| Node.js | `https://docs.outlit.ai/tracking/server/nodejs` |
| Rust / Tauri | `https://docs.outlit.ai/tracking/server/rust` |
| Ingest API | `https://docs.outlit.ai/api-reference/ingest` |
| Full docs index | `https://docs.outlit.ai/llms.txt` |
## Troubleshooting
### No browser events
1. Confirm the public env var prefix is correct and exposed to the client bundle.
2. Confirm provider/init runs once at app startup and wraps the app.
3. If `autoTrack: false`, call `enableTracking()` after consent.
4. Check Network for `/api/i/v1/<publicKey>/events`.
5. Check console warnings such as tracking not enabled, already initialized, or multiple instances.
### Events not linked to users
- Prefer `OutlitProvider user`, `useOutlitUser`, or `setUser()` for auth state.
- Send both `email` and `userId` when available.
- Include `customerId` for account/workspace-scoped products.
- Browser identity links visitor history; server identity does not replace browser identity.
- For native/device history, reuse the same persistent `fingerprint` before and after login.
### Server events missing
- Use `publicKey`, not `privateKey`.
- Use `eventName`, not `event`.
- Provide at least one of `email`, `userId`, `fingerprint`, or `customerId` to `track()`.
- `identify()` needs `email` or `userId`.
- Always `await outlit.flush()` before serverless handlers return.
- Non-retryable ingest errors stop automatic retries until restart; fix credentials/config instead of waiting.
### Activation missing or delayed
- Call activation after identity is set.
- In browsers, `user.activate()` can queue until `setUser()`/`identify()` provides identity.
- Do not use deprecated `engaged`/`inactive` calls; send product activity with `track()`.
## Key Principles
- Minimal changes: instrument the existing app shape.
- Detect first, ask second.
- Use `customerId` for account/workspace context.
- Use `setUser`/provider user for auth lifecycle.
- Use `track()` for product activity; use `user.activate()` only for the activation milestone.
- Flush server/native queues before exit.
- Keep events `snake_case`.
## Installation
Install this skill through the Outlit CLI when possible:
```sh
outlit setup skills
```
Or with the Skills CLI:
```sh
npx -y skills add https://github.com/OutlitAI/outlit-agent-skills --skill outlit-sdk -g
```
don't have the plugin yet? install it then click "run inline in claude" again.