Skip to main content

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)
Use server-side tracking for events that happen in your backend: subscription changes, feature usage from APIs, background jobs, etc.

Installation

npm install --save @outlit/node

Quick Start

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',
  eventName: 'subscription_upgraded',
  properties: {
    fromPlan: 'starter',
    toPlan: 'pro',
    mrr: 99
  }
})

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

Configuration

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

Options

publicKey
string
required
Your organization’s public key.
apiHost
string
default:"https://app.outlit.ai"
API endpoint for sending events.
flushInterval
number
default:"10000"
How often to automatically flush events (milliseconds).
maxBatchSize
number
default:"100"
Maximum events per batch before auto-flush.
timeout
number
default:"10000"
HTTP request timeout in milliseconds.

API Reference

outlit.track(options)

Track a custom event for a user, a customer, or both.
outlit.track({
  email: 'jane@acme.com',
  userId: 'usr_12345', // optional if email provided
  customerId: 'cust_123',
  eventName: 'report_exported',
  properties: {
    reportType: 'sales',
    format: 'pdf',
    rowCount: 1500
  },
  timestamp: Date.now() // optional, defaults to now
})
email
string
User’s email address. Resolves immediately to a customer profile.
userId
string
Your system-owned user/contact ID.
fingerprint
string
Device identifier for anonymous tracking. See Device Tracking.
customerId
string
Your system-owned customer/account/workspace ID.
eventName
string
required
Name of the event. Use snake_case.
properties
Record<string, any>
Additional data to attach to the event.
timestamp
number
Unix timestamp in milliseconds. Defaults to current time.
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.

outlit.identify(options)

Update user traits without tracking an event. Customer metadata can be included alongside the user identity.
outlit.identify({
  email: 'jane@acme.com',
  userId: 'usr_12345',
  customerId: 'cust_123',
  customerTraits: {
    plan: 'enterprise',
    seats: 50
  },
  traits: {
    name: 'Jane Doe',
    role: 'admin'
  }
})
email
string
User’s email address. Use this or userId to establish user-scoped identity.
userId
string
Your system-owned user/contact ID.
fingerprint
string
Device identifier for linking anonymous events. See Device Tracking.
traits
Record<string, any>
Properties to update on the user profile.
customerId
string
Your system-owned customer/account/workspace ID. Send this with email or userId to link the account/workspace to the resolved contact profile.
customerTraits
Record<string, any>
Properties to update on the customer profile.
At least one of email or userId is required for identify(). Customer fields are optional additions.

Contact Stage Methods

Track contact progression through your product lifecycle. These methods require 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.
outlit.user.activate({
  email: 'user@example.com',
  properties: { flow: 'onboarding' }
})

outlit.user.engaged(options)

Mark a contact as engaged. Typically called when a user reaches a usage milestone. Can also be computed automatically based on activity.
outlit.user.engaged({
  userId: 'usr_123',
  properties: { milestone: 'first_project_created' }
})

outlit.user.inactive(options)

Mark a contact as inactive. Use this when a user’s engagement has lapsed.
outlit.user.inactive({
  email: 'user@example.com',
  properties: { reason: 'no_login_30_days' }
})
The discovered and signed_up stages are automatically inferred from identify calls.

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.
outlit.customer.trialing({
  customerId: 'cust_123',
  properties: { trialEndsAt: '2024-02-15' }
})

outlit.customer.paid(options)

Mark an account as paying.
outlit.customer.paid({
  customerId: 'cust_123',
  properties: { plan: 'pro', amount: 99 }
})

outlit.customer.churned(options)

Mark an account as churned.
outlit.customer.churned({
  customerId: 'cust_123',
  properties: { reason: 'cancelled_by_user' }
})
Billing methods target accounts by customerId. Contacts linked to that customer share the same billing status. See Customer Journey for details on how contact stages and account billing work together.

outlit.flush()

Immediately send all queued events. Call this before your process exits.
await outlit.flush()

outlit.shutdown()

Gracefully shutdown: flushes remaining events and stops the timer.
await outlit.shutdown()

outlit.queueSize

Get the number of events waiting to be sent.
console.log(`Pending events: ${outlit.queueSize}`)

Framework Examples

Express.js

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

// 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)

In serverless environments, always call flush() before returning. The function may be terminated before the background flush runs.
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

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.
Track payment and subscription events from Stripe:
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)

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:
EventWhenProperties
subscription_createdNew subscriptionplan, price, interval
subscription_upgradedPlan upgradefromPlan, toPlan, mrr
subscription_cancelledCancellationreason, mrr_lost
payment_succeededPayment receivedamount, currency
payment_failedPayment failedamount, error_code
feature_usedFeature usagefeature, count
api_key_createdAPI key createdkeyName, permissions
export_completedData exportformat, rowCount, size
invite_sentTeam inviteinviteeEmail, role
user_role_changedPermission changefromRole, toRole

Error Handling

The SDK silently handles errors to not disrupt your application. For debugging:
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:
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',
  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',
  properties: { plan: 'pro' }
}

outlit.customer.paid(billingOptions)

Next Steps

Identity Resolution

Learn how profiles are merged across sources

Customer Journey

Understand contact stages and account billing