> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outlit.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js SDK

> Server-side tracking for backend applications

## Overview

The Node.js SDK is designed for server-side tracking where you have user identity. It supports:

* **Identified tracking** - Use `email` or `userId` for user-scoped resolution
* **Customer attribution** - Add `customerId` for account/workspace context, even before a user is known
* **Efficient batching** - Events are queued and sent in batches
* **Device tracking** - Use `fingerprint` for desktop/mobile apps ([learn more](/concepts/website-visitors#device-tracking-non-browser))

<Info>
  Use server-side tracking for events that happen in your backend: subscription changes, feature usage from APIs, background jobs, etc.
</Info>

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install --save @outlit/node
  ```

  ```bash pnpm theme={null}
  pnpm add @outlit/node
  ```

  ```bash yarn theme={null}
  yarn add @outlit/node
  ```

  ```bash bun theme={null}
  bun add @outlit/node
  ```
</CodeGroup>

## Quick Start

```typescript theme={null}
import { Outlit } from '@outlit/node'

const outlit = new Outlit({
  publicKey: 'pk_your_public_key'
})

// Track an event
outlit.track({
  email: 'user@example.com',
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  eventName: 'subscription_upgraded',
  properties: {
    fromPlan: 'starter',
    toPlan: 'pro',
    mrr: 99
  }
})

// Important: flush before process exits
await outlit.flush()
```

## Configuration

```typescript theme={null}
const outlit = new Outlit({
  publicKey: 'pk_xxx',
  flushInterval: 10000, // ms, default 10s
  maxBatchSize: 100, // events per batch
  timeout: 10000, // request timeout ms
})
```

### Options

<ParamField path="publicKey" type="string" required>
  Your organization's public key.
</ParamField>

<ParamField path="apiHost" type="string" default="https://app.outlit.ai">
  API endpoint for sending events.
</ParamField>

<ParamField path="flushInterval" type="number" default="10000">
  How often to automatically flush events (milliseconds).
</ParamField>

<ParamField path="maxBatchSize" type="number" default="100">
  Maximum events per batch before auto-flush.
</ParamField>

<ParamField path="timeout" type="number" default="10000">
  HTTP request timeout in milliseconds.
</ParamField>

## API Reference

### `outlit.track(options)`

Track a custom event for a user, a customer, or both.

```typescript theme={null}
outlit.track({
  email: 'jane@acme.com',
  userId: 'usr_12345', // optional if email provided
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  eventName: 'report_exported',
  properties: {
    reportType: 'sales',
    format: 'pdf',
    rowCount: 1500
  },
  timestamp: Date.now() // optional, defaults to now
})
```

<ParamField path="email" type="string">
  User's email address. Resolves immediately to a customer profile.
</ParamField>

<ParamField path="userId" type="string">
  Your system-owned user/contact ID.
</ParamField>

<ParamField path="fingerprint" type="string">
  Device identifier for anonymous tracking. See [Device Tracking](/concepts/website-visitors#device-tracking-non-browser).
</ParamField>

<ParamField path="customerId" type="string">
  Your system-owned customer/account/workspace ID.
</ParamField>

<ParamField path="eventName" type="string" required>
  Name of the event. Use `snake_case`.
</ParamField>

<ParamField path="properties" type="Record<string, any>">
  Additional data to attach to the event.
</ParamField>

<ParamField path="timestamp" type="number">
  Unix timestamp in milliseconds. Defaults to current time.
</ParamField>

<Info>
  At least one of `email`, `userId`, `fingerprint`, or `customerId` is required for `track()`. `customerId`-only events are valid immediately and can later link to a resolved customer when `identify()` uses the same `customerId` with an email.
</Info>

### `outlit.identify(options)`

Update user traits without tracking an event. Customer metadata can be included alongside the user identity.

```typescript theme={null}
outlit.identify({
  email: 'jane@acme.com',
  userId: 'usr_12345',
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  customerTraits: {
    plan: 'enterprise',
    seats: 50
  },
  traits: {
    name: 'Jane Doe',
    role: 'admin'
  }
})
```

<ParamField path="email" type="string">
  User's email address. Use this or `userId` to establish user-scoped identity.
</ParamField>

<ParamField path="userId" type="string">
  Your system-owned user/contact ID.
</ParamField>

<ParamField path="fingerprint" type="string">
  Device identifier for linking anonymous events. See [Device Tracking](/concepts/website-visitors#device-tracking-non-browser).
</ParamField>

<ParamField path="traits" type="Record<string, any>">
  Properties to update on the user profile.
</ParamField>

<ParamField path="customerId" type="string">
  Your system-owned customer/account/workspace ID. Send this with `email` or `userId` to link the account/workspace to the resolved contact profile.
</ParamField>

<ParamField path="customerTraits" type="Record<string, any>">
  Properties to update on the customer profile.
</ParamField>

<Info>
  At least one of `email` or `userId` is required for `identify()`. Customer fields are optional additions.
</Info>

### Lifecycle and billing

Send ordinary product facts with `track()`. For activation, choose the ordinary event that represents your product's value milestone and track it after the action succeeds:

```typescript theme={null}
outlit.track({
  email: 'user@example.com',
  eventName: 'onboarding_completed',
  properties: { flow: 'onboarding' }
})
```

<Info>
  Outlit Core derives activation from the customer-selected ordinary event and derives engagement and inactivity from activity. Billing status comes from verified integrations such as Stripe. See [Customer Journey](/concepts/customer-journey).
</Info>

### `outlit.flush()`

Immediately send all queued events. **Call this before your process exits.**

```typescript theme={null}
await outlit.flush()
```

### `outlit.shutdown()`

Gracefully shutdown: flushes remaining events and stops the timer.

```typescript theme={null}
await outlit.shutdown()
```

### `outlit.queueSize`

Get the number of events waiting to be sent.

```typescript theme={null}
console.log(`Pending events: ${outlit.queueSize}`)
```

## Framework Examples

### Express.js

```typescript theme={null}
import express from 'express'
import { Outlit } from '@outlit/node'

const app = express()
const outlit = new Outlit({ publicKey: 'pk_xxx' })

// Middleware to attach tracker to request
app.use((req, res, next) => {
  req.outlit = outlit
  next()
})

app.post('/api/reports/export', async (req, res) => {
  const { user, format } = req.body

  // Your export logic
  const report = await generateReport(format)

  // Track the event
  req.outlit.track({
    email: user.email,
    eventName: 'report_exported',
    properties: { format, size: report.size }
  })

  res.json({ success: true })
})

// Graceful shutdown
process.on('SIGTERM', async () => {
  await outlit.shutdown()
  process.exit(0)
})
```

### Next.js API Routes

```typescript theme={null}
// app/api/subscription/upgrade/route.ts
import { Outlit } from '@outlit/node'

const outlit = new Outlit({ publicKey: process.env.OUTLIT_KEY! })

export async function POST(request: Request) {
  const { userId, email, customerId, newPlan } = await request.json()

  // Your upgrade logic
  await upgradeSubscription(userId, newPlan)

  // Track the event
  outlit.track({
    email,
    userId,
    customerId,
    eventName: 'subscription_upgraded',
    properties: { plan: newPlan }
  })

  // Flush immediately (serverless)
  await outlit.flush()

  return Response.json({ success: true })
}
```

### Serverless Functions (AWS Lambda, Vercel)

<Warning>
  In serverless environments, **always call `flush()` before returning**. The function may be terminated before the background flush runs.
</Warning>

```typescript theme={null}
import { Outlit } from '@outlit/node'

const outlit = new Outlit({ publicKey: process.env.OUTLIT_KEY! })

export async function handler(event) {
  const { email, action } = JSON.parse(event.body)

  outlit.track({
    email,
    eventName: action,
    properties: { source: 'lambda' }
  })

  // Critical: flush before returning
  await outlit.flush()

  return { statusCode: 200, body: 'OK' }
}
```

### Stripe Webhooks

<Info>
  Connect Stripe as a verified Outlit integration. Subscription state then updates account billing status automatically; do not translate Stripe webhooks into authoritative SDK billing commands.
</Info>

### Background Jobs (Bull, Agenda)

```typescript theme={null}
import { Queue, Worker } from 'bullmq'
import { Outlit } from '@outlit/node'

const outlit = new Outlit({ publicKey: 'pk_xxx' })

const worker = new Worker('emails', async (job) => {
  const { email, templateId } = job.data

  // Your job logic
  await sendEmail(email, templateId)

  // Track completion
  outlit.track({
    email,
    eventName: 'email_sent',
    properties: { templateId, jobId: job.id }
  })
})

// Periodic flush (events batch automatically)
// No need to flush per job unless critical
```

## Common Events to Track

Here are recommended server-side events:

| Event                    | When              | Properties                   |
| ------------------------ | ----------------- | ---------------------------- |
| `subscription_created`   | New subscription  | `plan`, `price`, `interval`  |
| `subscription_upgraded`  | Plan upgrade      | `fromPlan`, `toPlan`, `mrr`  |
| `subscription_cancelled` | Cancellation      | `reason`, `mrr_lost`         |
| `payment_succeeded`      | Payment received  | `amount`, `currency`         |
| `payment_failed`         | Payment failed    | `amount`, `error_code`       |
| `feature_used`           | Feature usage     | `feature`, `count`           |
| `api_key_created`        | API key created   | `keyName`, `permissions`     |
| `export_completed`       | Data export       | `format`, `rowCount`, `size` |
| `invite_sent`            | Team invite       | `inviteeEmail`, `role`       |
| `user_role_changed`      | Permission change | `fromRole`, `toRole`         |

## Error Handling

The SDK silently handles errors to not disrupt your application. For debugging:

```typescript theme={null}
const outlit = new Outlit({ publicKey: 'pk_xxx' })

// Events are queued even if the network is down
outlit.track({ email: 'user@test.com', eventName: 'test' })

// Check queue size to detect potential issues
if (outlit.queueSize > 500) {
  console.warn('Outlit queue backing up - possible connectivity issue')
}
```

## TypeScript Support

Full TypeScript support is included:

```typescript theme={null}
import {
  Outlit,
  type OutlitOptions,
  type ServerTrackOptions,
  type ServerIdentifyOptions,
} from '@outlit/node'

const options: OutlitOptions = {
  publicKey: 'pk_xxx',
  flushInterval: 5000,
}

const outlit = new Outlit(options)

const trackOptions: ServerTrackOptions = {
  email: 'user@test.com',
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  eventName: 'test',
  properties: { key: 'value' }
}

outlit.track(trackOptions)
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Identity Resolution" icon="fingerprint" href="/concepts/identity-resolution">
    Learn how profiles are merged across sources
  </Card>

  <Card title="Customer Journey" icon="route" href="/concepts/customer-journey">
    Understand contact stages and account billing
  </Card>
</CardGroup>
