> ## Documentation Index
> Fetch the complete documentation index at: https://outlit-codex-platform-actions-create-update-cli.mintlify.site/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>

### Activation

Mark activation after a contact completes your product's activation milestone. This method requires at least one of `fingerprint`, `email`, or `userId`.

#### `outlit.user.activate(options)`

Mark a contact as activated. Typically called after a user completes onboarding or a key activation milestone.

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

<Info>
  The `discovered` and `signed_up` stages are automatically inferred from identify calls. Use `track()` for product activity. Outlit handles engagement and inactivity automatically; see [Customer Journey](/concepts/customer-journey#automatic-engagement-and-inactivity).
</Info>

### Account Billing Methods

Track account billing status. These methods target the account by `customerId`, not individual contacts. If you've connected Stripe, billing is handled automatically.

#### `outlit.customer.trialing(options)`

Mark an account as trialing.

```typescript theme={null}
outlit.customer.trialing({
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  properties: { trialEndsAt: '2024-02-15' }
})
```

#### `outlit.customer.paid(options)`

Mark an account as paying.

```typescript theme={null}
outlit.customer.paid({
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  properties: { plan: 'pro', amount: 99 }
})
```

#### `outlit.customer.churned(options)`

Mark an account as churned.

```typescript theme={null}
outlit.customer.churned({
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  properties: { reason: 'cancelled_by_user' }
})
```

<Info>
  Billing methods target accounts by `customerId`. Contacts linked to that customer share the same billing status. See [Customer Journey](/concepts/customer-journey) for details on how contact stages and account billing work together.
</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, amount } = await request.json()

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

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

  // Update account billing status
  outlit.customer.paid({
    customerId,
    properties: { plan: newPlan, amount }
  })

  // 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>
  If you've connected Stripe to Outlit, billing status is handled automatically—you don't need to implement webhook handling yourself. The example below is only needed for custom billing logic.
</Info>

Track payment and subscription events from Stripe:

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

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

export async function handleStripeWebhook(event: Stripe.Event) {
  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session
      const customerId =
        typeof session.customer === 'string' ? session.customer : session.customer?.id
      if (!customerId) break
      outlit.customer.paid({
        customerId,
        properties: {
          ...(session.amount_total != null
            ? { amount: session.amount_total / 100 }
            : {}),
          ...(session.currency != null ? { currency: session.currency } : {}),
        }
      })
      break
    }

    case 'customer.subscription.deleted': {
      const subscription = event.data.object as Stripe.Subscription
      const customerId =
        typeof subscription.customer === 'string'
          ? subscription.customer
          : subscription.customer?.id
      if (!customerId) break
      outlit.customer.churned({
        customerId,
        properties: {
          ...(subscription.cancellation_details?.reason != null
            ? { reason: subscription.cancellation_details.reason }
            : {}),
        }
      })
      break
    }
  }

  await outlit.flush()
}
```

### 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,
  type StageOptions,
  type BillingOptions,
} 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)

// Contact stage options
const stageOptions: StageOptions = {
  email: 'user@test.com',
  properties: { flow: 'onboarding' }
}

outlit.user.activate(stageOptions)

// Account billing options
const billingOptions: BillingOptions = {
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  properties: { plan: 'pro' }
}

outlit.customer.paid(billingOptions)
```

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