Install
openclaw skills install outlit-sdkUse 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.
openclaw skills install outlit-sdkDecision-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.
This skill has no runtime credential requirement of its own. Environment variable names in this guide, such as NEXT_PUBLIC_OUTLIT_KEY or OUTLIT_KEY, are examples for the target application being instrumented.
Before anything else, check whether Outlit is already installed:
@outlit/browser, @outlit/node, outlit in Cargo.toml, https://cdn.outlit.ai, or existing outlit.init(...) / new Outlit(...) calls.Ask what they need help with, then run detection before changing code:
These points prevent the most common stale integrations:
publicKey values that start with pk_. Do not use privateKey, OUTLIT_PRIVATE_KEY, visitorId, or event for @outlit/node examples.new Outlit({ publicKey }) and track({ eventName, email | userId | fingerprint | customerId, properties }).track() requires at least one of email, userId, fingerprint, or customerId.identify() requires email or userId. customerId is optional account/workspace attribution, not a user identity by itself.customerId for your system-owned account/workspace/customer ID. The TypeScript SDK no longer uses customerDomain; do not add it.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.customerId; stripeCustomerId is compatibility-only. In Rust billing methods still start with a domain and can add .customer_id(...).snake_case. SDK events get UUIDv7 event IDs automatically; do not generate event IDs manually.Goal: get data flowing quickly so the user can see Outlit connection/activity without making product-strategy decisions.
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 |
| 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.
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 |
Fetch the framework doc from the Doc URL Map and implement only startup initialization:
OutlitProvider from @outlit/browser/react in a client boundary.OutlitPlugin from @outlit/browser/vue.@outlit/browser or CDN script.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.
Verify with one or more:
https://app.outlit.ai/api/i/v1/<publicKey>/events returning success.[Outlit] warnings.await outlit.flush() or await outlit.shutdown() before process/serverless exit.Then ask whether to continue with the 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 |
| 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.
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.
| 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.
Activation means a user reached the product's meaningful value moment. It is not automatically "completed onboarding" unless onboarding itself delivers value.
user.activate() only after the action is confirmed complete, usually after the backend succeeds.user.engaged() or user.inactive() in new integrations.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:
outlit.customer.paid({
customerId: "cust_123",
properties: { plan: "pro" },
})
Rust currently differs:
client.customer()
.paid("acme.com")
.customer_id("cust_123")
.send()
.await?;
Always flush server/native queues before the handler exits.
Browser SDK defaults already capture:
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.
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.
Use server/native tracking for backend-confirmed product activity, billing, background jobs, native apps, CLIs, and device-based activity.
Node example:
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:
outlit.track({
fingerprint: deviceId,
eventName: "app_opened",
})
outlit.identify({
email: user.email,
fingerprint: deviceId,
})
Serverless rule: await outlit.flush() before returning. Long-lived process rule: reuse one client and call shutdown() on graceful shutdown.
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 |
autoTrack: false, call enableTracking() after consent./api/i/v1/<publicKey>/events.OutlitProvider user, useOutlitUser, or setUser() for auth state.email and userId when available.customerId for account/workspace-scoped products.fingerprint before and after login.publicKey, not privateKey.eventName, not event.email, userId, fingerprint, or customerId to track().identify() needs email or userId.await outlit.flush() before serverless handlers return.user.activate() can queue until setUser()/identify() provides identity, but the queue is bounded.engaged/inactive calls; send product activity with track().customerId for account/workspace context.setUser/provider user for auth lifecycle.track() for product activity; use user.activate() only for the activation milestone.snake_case.Install this skill through the Outlit CLI when possible:
outlit setup skills
Or with the Skills CLI:
npx -y skills add https://github.com/OutlitAI/outlit-agent-skills --skill outlit-sdk -g