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 and/or customerDomain for account context
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' ,
customerDomain: 'acme.com' ,
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' ,
apiHost: 'https://app.outlit.ai' , // optional
flushInterval: 10000 , // ms, default 10s
maxBatchSize: 100 , // events per batch
timeout: 10000 , // request timeout ms
})
Options
Your organization’s public key.
apiHost
string
default: "https://app.outlit.ai"
API endpoint for sending events.
How often to automatically flush events (milliseconds).
Maximum events per batch before auto-flush.
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' ,
customerDomain: 'acme.com' ,
eventName: 'report_exported' ,
properties: {
reportType: 'sales' ,
format: 'pdf' ,
rowCount: 1500
},
timestamp: Date . now () // optional, defaults to now
})
User’s email address. Resolves immediately to a customer profile.
Your system-owned user/contact ID.
Your system-owned customer/account/workspace ID.
Public customer/account domain used for billing and attribution.
Name of the event. Use snake_case.
Additional data to attach to the event.
Unix timestamp in milliseconds. Defaults to current time.
At least one of email, userId, fingerprint, customerId, or customerDomain is required for track().
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' ,
customerDomain: 'acme.com' ,
customerTraits: {
plan: 'enterprise' ,
seats: 50
},
traits: {
name: 'Jane Doe' ,
role: 'admin'
}
})
User’s email address. Use this or userId to establish user-scoped identity.
Your system-owned user/contact ID.
Properties to update on the user profile.
Your system-owned customer/account/workspace ID.
Public customer/account domain used for billing and attribution.
Properties to update on the customer profile.
At least one of email or userId is required for identify(). Customer fields are optional additions.
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 and/or customerDomain, not individual contacts. If you’ve connected Stripe, billing is handled automatically.
outlit.customer.trialing(options)
Mark an account as trialing.
outlit . customer . trialing ({
customerDomain: 'acme.com' ,
customerId: 'cust_123' ,
properties: { trialEndsAt: '2024-02-15' }
})
outlit.customer.paid(options)
Mark an account as paying.
outlit . customer . paid ({
customerDomain: 'acme.com' ,
customerId: 'cust_123' ,
properties: { plan: 'pro' , amount: 99 }
})
outlit.customer.churned(options)
Mark an account as churned.
outlit . customer . churned ({
customerDomain: 'acme.com' ,
customerId: 'cust_123' ,
properties: { reason: 'cancelled_by_user' }
})
Billing methods target accounts by customerId and/or customerDomain. All contacts under that domain 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.
outlit.shutdown()
Gracefully shutdown: flushes remaining events and stops the timer.
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 , newPlan , amount , domain } = await request . json ()
// Your upgrade logic
await upgradeSubscription ( userId , newPlan )
// Track the event
outlit . track ({
email ,
userId ,
customerId: `cust_ ${ userId } ` ,
customerDomain: domain ,
eventName: 'subscription_upgraded' ,
properties: { plan: newPlan }
})
// Update account billing status
outlit . customer . paid ({
customerDomain: domain ,
customerId: `cust_ ${ userId } ` ,
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
// Get account domain from your database
const domain = await getAccountDomain ( customerId )
if ( domain ) {
outlit . customer . paid ({
customerDomain: domain ,
customerId ,
properties: {
amount: session . amount_total ? session . amount_total / 100 : undefined ,
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
// Get account domain from your database
const domain = await getAccountDomain ( customerId )
if ( domain ) {
outlit . customer . churned ({
customerDomain: domain ,
customerId ,
properties: {
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:
Event When Properties subscription_createdNew subscription plan, price, intervalsubscription_upgradedPlan upgrade fromPlan, toPlan, mrrsubscription_cancelledCancellation reason, mrr_lostpayment_succeededPayment received amount, currencypayment_failedPayment failed amount, error_codefeature_usedFeature usage feature, countapi_key_createdAPI key created keyName, permissionsexport_completedData export format, rowCount, sizeinvite_sentTeam invite inviteeEmail, roleuser_role_changedPermission change fromRole, 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' ,
customerDomain: 'acme.com' ,
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 = {
customerDomain: 'acme.com' ,
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