Skip to main content

Node.js SDK

The Dreamdata Node.js SDK (@segment/analytics-node) provides a robust way to track events from your server-side applications. It handles batching, retries, and graceful shutdown automatically.

Runtime Support

The SDK is compatible with:

  • Node.js >= 18
  • AWS Lambda
  • Cloudflare Workers
  • Vercel Edge Functions
  • Web Workers (experimental)

Prerequisites

Before you begin, ensure you have:

  • A Dreamdata account
  • Your Dreamdata Source API Key (found in Data Platform > Sources > Server Side Analytics APIs)

Installation

Install the SDK using your preferred package manager:

# npm
npm install @segment/analytics-node

# yarn
yarn add @segment/analytics-node

# pnpm
pnpm install @segment/analytics-node

Quick Start

import { Analytics } from "@segment/analytics-node";

// Initialize the Analytics client
const analytics = new Analytics({
writeKey: "<YOUR_DREAMDATA_API_KEY>",
});

// Track an event
analytics.track({
userId: "user-123",
event: "Order Completed",
properties: {
orderId: "order-456",
revenue: 99.99,
},
});

// Identify a user
analytics.identify({
userId: "user-123",
traits: {
email: "user@example.com",
name: "Jane Doe",
plan: "enterprise",
},
});

Using CommonJS

const { Analytics } = require("@segment/analytics-node");

const analytics = new Analytics({
writeKey: "<YOUR_DREAMDATA_API_KEY>",
});

API Methods

identify()

Associates a user with their traits and records when they are first recognized.

analytics.identify({
userId: "user-123",
traits: {
email: "user@example.com",
name: "Jane Doe",
company: "Acme Inc",
plan: "enterprise",
},
});

Parameters:

ParameterTypeRequiredDescription
userIdstringYes*Unique identifier for the user
anonymousIdstringYes*Anonymous identifier (if userId not available)
traitsobjectNoUser attributes (email, name, company, etc.)
contextobjectNoExtra context (ip, userAgent, campaign, etc.)
timestampISO DateNoWhen the event occurred (defaults to now)
integrationsobjectNoControl which integrations receive this event

*At least one of userId or anonymousId is required.

track()

Records actions your users perform.

analytics.track({
userId: "user-123",
event: "Article Read",
properties: {
title: "Getting Started with B2B Analytics",
category: "Documentation",
readTime: 245,
},
});

Parameters:

ParameterTypeRequiredDescription
userIdstringYes*Unique identifier for the user
anonymousIdstringYes*Anonymous identifier
eventstringYesName of the action being tracked
propertiesobjectNoProperties describing the event
contextobjectNoExtra context
timestampISO DateNoWhen the event occurred
integrationsobjectNoControl integrations

page()

Records page views on your website.

analytics.page({
userId: "user-123",
category: "Docs",
name: "API Reference",
properties: {
url: "https://example.com/docs/api",
referrer: "https://google.com",
},
});

Parameters:

ParameterTypeRequiredDescription
userIdstringYes*Unique identifier for the user
anonymousIdstringYes*Anonymous identifier
categorystringNoCategory of the page
namestringNoName of the page
propertiesobjectNoPage properties (url, referrer, title, etc.)
contextobjectNoExtra context
timestampISO DateNoWhen the page was viewed

group()

Associates a user with a company or organization.

analytics.group({
userId: "user-123",
groupId: "company-456",
traits: {
name: "Acme Inc",
industry: "Technology",
employees: 150,
plan: "enterprise",
},
});

Parameters:

ParameterTypeRequiredDescription
userIdstringYes*Unique identifier for the user
anonymousIdstringYes*Anonymous identifier
groupIdstringYesUnique identifier for the group/company
traitsobjectNoGroup attributes
contextobjectNoExtra context
timestampISO DateNoWhen the association occurred

alias()

Merges two user identities, connecting a known user to an anonymous one.

analytics.alias({
userId: "user-123",
previousId: "anon-xyz-789",
});

Parameters:

ParameterTypeRequiredDescription
userIdstringYesThe new user ID
previousIdstringYesThe previous ID (userId or anonymousId)
contextobjectNoExtra context
timestampISO DateNoWhen the alias occurred

screen()

Records screen views in mobile or desktop applications.

analytics.screen({
userId: "user-123",
category: "Settings",
name: "Profile",
properties: {
variation: "dark-mode",
},
});

Configuration

The Analytics constructor accepts a settings object with the following options:

const analytics = new Analytics({
writeKey: "<YOUR_API_KEY>",
host: "https://cdn.dreamdata.cloud/api",
path: "/v1/batch",
flushAt: 15,
flushInterval: 10000,
maxRetries: 3,
httpRequestTimeout: 10000,
disable: false,
});
OptionTypeDefaultDescription
writeKeystringRequiredYour Dreamdata API key
hoststringhttps://cdn.dreamdata.cloud/apiAPI endpoint
pathstring/v1/batchAPI path
flushAtnumber15Number of events to queue before sending (alias: maxEventsInBatch)
flushIntervalnumber10000Milliseconds to wait before flushing (10 seconds)
maxRetriesnumber3Number of retry attempts for failed requests
httpRequestTimeoutnumber10000Request timeout in milliseconds
disablebooleanfalseDisable all tracking (useful for development)
httpClientobjectBuilt-in fetch clientCustom HTTP client implementation
oauthSettingsobjectundefinedOAuth 2 configuration (see advanced section)

Error Handling

The SDK emits events that you can listen to for debugging and error handling:

const analytics = new Analytics({
writeKey: "<YOUR_API_KEY>",
});

// Handle errors
analytics.on("error", (err) => {
console.error("Analytics error:", err);
});

// Debug HTTP requests
analytics.on("http_request", (request) => {
console.log("Request:", request.url, request.method);
});

// Detect events sent after close
analytics.on("call_after_close", (event) => {
console.warn("Event sent after analytics was closed:", event);
});

Flushing and Graceful Shutdown

flush()

Manually flush all queued events without closing the client:

await analytics.flush();

closeAndFlush()

Flush all events and close the client. Call this before your application exits:

// Graceful shutdown
process.on("SIGTERM", async () => {
await analytics.closeAndFlush();
process.exit(0);
});

Both methods accept an optional timeout parameter:

// Wait up to 5 seconds for flush to complete
await analytics.closeAndFlush({ timeout: 5000 });

Serverless Environments

Serverless environments require special handling because the runtime may terminate before events are sent. The key is to await the callback or use flushAt: 1 to send events immediately.

AWS Lambda

import { Analytics } from "@segment/analytics-node";

// Create a new instance per request to avoid state issues
const createAnalytics = () =>
new Analytics({
writeKey: "<YOUR_API_KEY>",
flushAt: 1, // Send immediately
}).on("error", console.error);

export const handler = async (event) => {
const analytics = createAnalytics();

// Await the callback to ensure the event is sent
await new Promise((resolve) =>
analytics.track(
{
userId: event.userId,
event: "Lambda Invoked",
properties: { functionName: "my-function" },
},
resolve,
),
);

return {
statusCode: 200,
body: JSON.stringify({ success: true }),
};
};

Vercel Edge Functions

import { Analytics } from "@segment/analytics-node";
import { NextRequest, NextResponse } from "next/server";

const analytics = new Analytics({
writeKey: "<YOUR_API_KEY>",
flushAt: 1,
}).on("error", console.error);

export const config = {
runtime: "edge",
};

export default async (req: NextRequest) => {
await new Promise((resolve) =>
analytics.track(
{
userId: "user-123",
event: "Edge Function Called",
},
resolve,
),
);

return NextResponse.json({ success: true });
};

Cloudflare Workers

import { Analytics } from "@segment/analytics-node";

export default {
async fetch(request: Request, env: Env): Promise<Response> {
const analytics = new Analytics({
writeKey: env.DREAMDATA_API_KEY,
flushAt: 1,
}).on("error", console.error);

await new Promise((resolve) =>
analytics.track(
{
userId: "user-123",
event: "Worker Invoked",
},
resolve,
),
);

return new Response(JSON.stringify({ success: true }), {
headers: { "Content-Type": "application/json" },
});
},
};

OAuth 2 Authentication

For enhanced security between your server and Dreamdata's Tracking API, you can enable OAuth 2 authentication. This uses a signed client assertion JWT for non-interactive server environments.

Setup

  1. Enable OAuth 2 in your Dreamdata workspace
  2. Upload your public key to the Dreamdata dashboard
  3. Keep your private key secure on your server
import { Analytics, OAuthSettings } from "@segment/analytics-node";
import { readFileSync } from "fs";

const privateKey = readFileSync("private.pem", "utf8");

const oauthSettings: OAuthSettings = {
clientId: "<CLIENT_ID_FROM_DASHBOARD>",
clientKey: privateKey,
keyId: "<PUBLIC_KEY_ID_FROM_DASHBOARD>",
};

const analytics = new Analytics({
writeKey: "<YOUR_API_KEY>",
oauthSettings,
});

analytics.on("error", (err) => {
console.error("OAuth or tracking error:", err);
});

analytics.track({
userId: "user-123",
event: "Secure Event",
});

OAuth Settings

OptionTypeRequiredDescription
clientIdstringYesOAuth App ID from Dreamdata dashboard
clientKeystringYesYour private key (PEM format)
keyIdstringYesPublic key ID from Dreamdata dashboard
authServerstringNoAuthorization server URL
scopestringNoPermission scope (default: tracking_api:write)
maxRetriesnumberNoToken request retry attempts
httpClientobjectNoCustom HTTP client for auth requests

Generating Identifiers

For purely server-side integrations, you need to generate and manage user identifiers yourself. The SDK requires either userId or anonymousId for every event.

Using userId

Use userId when you have an authenticated user. This should be a stable identifier from your system (database ID, auth provider ID, etc.):

analytics.track({
userId: user.id, // From your auth system
event: "Feature Used",
properties: { feature: "export" },
});

Generating anonymousId

For anonymous visitors, generate a UUID and persist it across requests:

import { randomUUID } from "crypto";

// Generate a new anonymous ID
const anonymousId = randomUUID(); // e.g., "550e8400-e29b-41d4-a716-446655440000"

Maintaining Identity Across Requests

Store the anonymousId in a session or cookie to maintain consistency:

import express from "express";
import session from "express-session";
import { randomUUID } from "crypto";
import { Analytics } from "@segment/analytics-node";

const app = express();
const analytics = new Analytics({ writeKey: "<YOUR_API_KEY>" });

app.use(
session({
secret: "your-secret",
resave: false,
saveUninitialized: true,
}),
);

// Middleware to ensure anonymousId exists
app.use((req, res, next) => {
if (!req.session.anonymousId) {
req.session.anonymousId = randomUUID();
}
next();
});

app.get("/products/:id", (req, res) => {
analytics.track({
anonymousId: req.session.anonymousId,
event: "Product Viewed",
properties: { productId: req.params.id },
});

res.json({ product: req.params.id });
});

Linking Anonymous to Known Users

When a user logs in, call alias to connect their anonymous activity to their known identity:

app.post("/login", async (req, res) => {
const user = await authenticateUser(req.body);

// Link anonymous ID to the authenticated user
analytics.alias({
userId: user.id,
previousId: req.session.anonymousId,
});

// Future events use userId
analytics.identify({
userId: user.id,
traits: { email: user.email, name: user.name },
});

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

Cookieless Tracking (Company-Level Only)

For privacy-first tracking without individual identification, use a static placeholder and rely on IP-to-company lookup:

analytics.track({
anonymousId: "cookieless", // Static placeholder
event: "Page Viewed",
context: {
ip: clientIp, // Required for company attribution
},
});

See Server-Side Cookieless Tracking for complete implementation details.

Cross-Domain Tracking

When your application spans multiple domains or subdomains, you need to ensure the anonymousId persists across them to maintain a unified user journey.

Cross-Subdomain Tracking

To track users across subdomains (e.g., www.example.comapp.example.com), set cookies at the top-level domain so they're shared:

import express from "express";
import cookieParser from "cookie-parser";
import { randomUUID } from "crypto";

const app = express();
app.use(cookieParser());

// Middleware for cross-subdomain anonymous ID
app.use((req, res, next) => {
const cookieName = "dd_anonymous_id";

if (!req.cookies[cookieName]) {
const anonId = randomUUID();

res.cookie(cookieName, anonId, {
domain: ".example.com", // Leading dot includes all subdomains
path: "/",
maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year
sameSite: "lax",
secure: true, // Requires HTTPS
httpOnly: true,
});

req.anonymousId = anonId;
} else {
req.anonymousId = req.cookies[cookieName];
}

next();
});

Key cookie options:

OptionValuePurpose
domain.example.comLeading dot shares cookie across subdomains
path/Available on all paths
sameSitelaxPrevents CSRF while allowing navigation
securetrueOnly sent over HTTPS
httpOnlytrueNot accessible via JavaScript (more secure)

Cross-TLD Tracking

Tracking across different top-level domains (e.g., example.comexample.io) requires explicit coordination since cookies cannot be shared across TLDs.

Option 1: URL Parameter

Pass the anonymousId via URL when linking between domains:

// On source domain (example.com)
app.get("/go-to-partner", (req, res) => {
const targetUrl = new URL("https://example.io/landing");
targetUrl.searchParams.set("dd_anon_id", req.anonymousId);
res.redirect(targetUrl.toString());
});

// On target domain (example.io)
app.use((req, res, next) => {
const urlAnonId = req.query.dd_anon_id as string;
const cookieAnonId = req.cookies.dd_anonymous_id;

if (urlAnonId && !cookieAnonId) {
// Use the ID from the source domain
res.cookie("dd_anonymous_id", urlAnonId, {
domain: ".example.io",
path: "/",
maxAge: 365 * 24 * 60 * 60 * 1000,
sameSite: "lax",
secure: true,
});
req.anonymousId = urlAnonId;
} else {
req.anonymousId = cookieAnonId || randomUUID();
}

next();
});

Option 2: Shared Backend Lookup

If both domains share a backend or database, store the mapping:

// When user authenticates or performs a linking action
async function linkDomains(userId: string, anonymousId: string) {
await db.userIdentities.upsert({
where: { visitorSessionId: anonymousId },
create: {
visitorSessionId: anonymousId,
linkedAnonymousIds: [anonymousId],
},
update: { linkedAnonymousIds: { push: anonymousId } },
});
}
Privacy Consideration

Cross-TLD tracking via URL parameters may expose the anonymous ID in browser history, logs, and referrer headers. Consider:

  • Using short-lived tokens instead of raw IDs
  • Implementing server-side redirects to strip parameters
  • Ensuring compliance with privacy regulations

Best Practices

  1. Always handle errors - Attach an error listener to catch and log issues
  2. Use graceful shutdown - Call closeAndFlush() before your application exits
  3. Set appropriate flush settings - Balance between real-time data and efficiency
  4. Include context - Add IP addresses and user agents for better attribution
  5. Use consistent user IDs - Maintain the same userId across your backend services

Example: Express.js Integration

import express from "express";
import { Analytics } from "@segment/analytics-node";

const app = express();
const analytics = new Analytics({
writeKey: "<YOUR_API_KEY>",
}).on("error", console.error);

app.use(express.json());

// Middleware to extract tracking context
app.use((req, res, next) => {
req.trackingContext = {
ip: req.ip,
userAgent: req.get("User-Agent"),
};
next();
});

app.post("/api/signup", (req, res) => {
const { email, name } = req.body;
const userId = generateUserId();

analytics.identify({
userId,
traits: { email, name },
context: req.trackingContext,
});

analytics.track({
userId,
event: "User Signed Up",
properties: { method: "email" },
context: req.trackingContext,
});

res.json({ userId });
});

// Graceful shutdown
process.on("SIGTERM", async () => {
await analytics.closeAndFlush();
process.exit(0);
});

app.listen(3000);