Node.js

JavaScript (Node.js) SDK

Official Klime SDK for JavaScript (Node.js). Track events, identify users, and associate them with groups.

Installation

npm install @klime/node

Quick Start

// ESM
import { KlimeClient } from "@klime/node";

// CommonJS
// const { KlimeClient } = require("@klime/node");

const client = new KlimeClient({
  writeKey: "your-write-key",
});

// Identify a user
client.identify("user_123", {
  email: "user@example.com",
  name: "Stefan",
});

// Track an event
client.track(
  "Button Clicked",
  {
    buttonName: "Sign up",
    plan: "pro",
  },
  { userId: "user_123" }
);

// Associate user with a group and set group traits
client.group(
  "org_456",
  { name: "Acme Inc", plan: "enterprise" },
  { userId: "user_123" }
);

// Or just link the user to a group (if traits are already set)
client.group("org_456", null, { userId: "user_123" });

// Shutdown gracefully
process.on("SIGTERM", async () => {
  await client.shutdown();
  process.exit(0);
});

Installation Prompt

Copy and paste this prompt into Cursor, Copilot, or your favorite AI editor to integrate Klime:

Integrate Klime for customer analytics. Klime tracks user activity to identify which customers are healthy vs at risk of churning.

ANALYTICS MODES (determine which applies):
- Companies & Teams: Your customers are companies with multiple team members (SaaS, enterprise tools)
  → Use identify() + group() + track()
- Individual Customers: Your customers are individuals with private accounts (consumer apps, creator tools)
  → Use identify() + track() only (no group() needed)

KEY CONCEPTS:
- Every track() call requires either userId OR groupId (no anonymous events)
- Use groupId alone for org-level events (webhooks, cron jobs, system metrics)
- group() links a user to a company AND sets company traits (only for Companies & Teams mode)
- Order doesn't matter - events before identify/group still get attributed correctly

BEST PRACTICES:
- Initialize client ONCE at app startup (singleton pattern)
- Store write key in KLIME_WRITE_KEY environment variable
- Call shutdown() on SIGTERM/SIGINT to flush remaining events

Install: npm install @klime/node

const { KlimeClient } = require("@klime/node");

// Initialize with your KLIME_WRITE_KEY environment variable
const client = new KlimeClient({ writeKey: "YOUR_WRITE_KEY" });

// Identify users at signup/login:
client.identify("usr_abc123", { email: "jane@acme.com", name: "Jane Smith" });

// Track key activities:
client.track("Report Generated", { report_type: "revenue" }, { userId: "usr_abc123" });
client.track("Feature Used", { feature: "export", format: "csv" }, { userId: "usr_abc123" });
client.track("Teammate Invited", { role: "member" }, { userId: "usr_abc123" });

// If Companies & Teams mode: link user to their company and set company traits
client.group("org_456", { name: "Acme Inc", plan: "enterprise" }, { userId: "usr_abc123" });

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

INTEGRATION WORKFLOW:

Phase 1: Discover
Explore the codebase to understand:
1. What framework is used? (Express, Hono, Fastify, Koa, Next.js, NestJS, etc.)
2. Where is user identity available? (e.g., req.user.id, ctx.state.user, session.userId, JWT payload)
3. Is this Companies & Teams or Individual Customers?
   - Look for: organization, workspace, tenant, team, account models → Companies & Teams (use group())
   - No company/org concept, just individual users → Individual Customers (skip group())
4. Where do core user actions happen? (route handlers, services, controllers)
5. Is there existing analytics? (search: segment, posthog, mixpanel, amplitude, .track)
Match your integration style to the framework's conventions.

Phase 2: Instrument
Add these calls using idiomatic patterns for the framework:
- Initialize client once at startup (Express: app.js, Next.js: instrumentation.ts, NestJS: module provider)
- identify() in auth/login success handler
- group() when user-org association is established (Companies & Teams mode only)
- track() for key user actions (see below)

WHAT TO TRACK:
Active engagement (primary): feature usage, resource creation, collaboration, completing flows
Session signals (secondary): login/session start, dashboard access - distinguishes "low usage" from "churned"
Do NOT track: every page view, every API request, health checks, background jobs

Phase 3: Verify
Confirm: client initialized, shutdown handled, identify/group/track calls added

Phase 4: Summarize
Report what you added:
- Files modified and what was added to each
- Events being tracked (list event names and what triggers them)
- How userId is obtained (and groupId if Companies & Teams mode)
- Any assumptions made or questions

API Reference

Constructor

new KlimeClient(config: {
  writeKey: string;              // Required: Your Klime write key
  endpoint?: string;              // Optional: API endpoint (default: https://i.klime.com)
  flushInterval?: number;        // Optional: Milliseconds between flushes (default: 2000)
  maxBatchSize?: number;         // Optional: Max events per batch (default: 20, max: 100)
  maxQueueSize?: number;         // Optional: Max queued events (default: 1000)
  retryMaxAttempts?: number;     // Optional: Max retry attempts (default: 5)
  retryInitialDelay?: number;    // Optional: Initial retry delay in ms (default: 1000)
  flushOnShutdown?: boolean;     // Optional: Auto-flush on SIGTERM/SIGINT (default: true)
  logger?: Logger;               // Optional: Custom logger (default: console with [Klime] prefix)
  onError?: (error, events) => void;   // Optional: Callback for batch failures
  onSuccess?: (response) => void;      // Optional: Callback for successful sends
})

Methods

track(event: string, properties?: object, options?: { userId?, groupId? })

Track an event. Events can be attributed in two ways:

  • User events: Provide userId to track user activity (most common)
  • Group events: Provide groupId without userId for organization-level events
// User event (most common)
client.track(
  "Button Clicked",
  {
    buttonName: "Sign up",
    plan: "pro",
  },
  { userId: "user_123" }
);

// Group event (for webhooks, cron jobs, system events)
client.track(
  "Events Received",
  {
    count: 100,
    source: "webhook",
  },
  { groupId: "org_456" }
);

Note: The groupId option can also be combined with userId for multi-tenant scenarios where you need to specify which organization context a user event occurred in.

identify(userId: string, traits?: object)

Identify a user with traits.

client.identify("user_123", {
  email: "user@example.com",
  name: "Stefan",
});

group(groupId: string, traits?: object, options?: { userId? })

Associate a user with a group and/or set group traits.

// Associate user with a group and set group traits (most common)
client.group(
  "org_456",
  { name: "Acme Inc", plan: "enterprise" },
  { userId: "user_123" }
);

// Just link a user to a group (traits already set or not needed)
client.group("org_456", null, { userId: "user_123" });

// Just update group traits (e.g., from a webhook or background job)
client.group("org_456", { plan: "enterprise", employeeCount: 50 });

flush(): Promise<void>

Manually flush queued events immediately.

await client.flush();

shutdown(): Promise<void>

Gracefully shutdown the client, flushing remaining events.

await client.shutdown();

getQueueSize(): number

Return the number of events currently in the queue.

const pending = client.getQueueSize();
console.log(`${pending} events waiting to be sent`);

Synchronous Methods

For cases where you need confirmation that events were sent (e.g., before process exit, in tests), use the synchronous variants:

trackSync(event, properties, options): Promise<BatchResponse>

Track an event synchronously. Returns BatchResponse or throws SendError.

const { SendError } = require("@klime/node");

try {
  const response = await client.trackSync(
    "Critical Action",
    { key: "value" },
    { userId: "user_123" }
  );
  console.log(`Sent! Accepted: ${response.accepted}`);
} catch (error) {
  if (error instanceof SendError) {
    console.error(`Failed to send: ${error.message}`);
  }
}

identifySync(userId, traits): Promise<BatchResponse>

Identify a user synchronously. Returns BatchResponse or throws SendError.

groupSync(groupId, traits, options): Promise<BatchResponse>

Associate a user with a group synchronously. Returns BatchResponse or throws SendError.

Features

  • Automatic Batching: Events are automatically batched and sent every 2 seconds or when the batch size reaches 20 events
  • Automatic Retries: Failed requests are automatically retried with exponential backoff
  • Process Exit Handling: Automatically flushes events on SIGTERM/SIGINT
  • Zero Dependencies: Uses only Node.js standard library (fetch for Node 18+, https/http for older versions)
  • Universal Module Support: Works with ESM and CommonJS out of the box

Performance

When you call track(), identify(), or group(), the SDK:

  1. Adds the event to an in-memory queue (microseconds)
  2. Returns immediately without waiting for network I/O

Events are sent to Klime's servers asynchronously via Node.js's event loop. This means:

  • No network blocking: HTTP requests happen asynchronously without blocking the event loop
  • No latency impact: Tracking calls add < 1ms to your request handling time
  • Automatic batching: Events are queued and sent in batches (default: every 2 seconds or 20 events)
// This returns immediately - no HTTP request is made here
client.track("Button Clicked", { button: "signup" }, { userId: "user_123" });

// Your code continues without waiting
res.json({ success: true });

The only blocking operation is await flush(), which waits for all queued events to be sent. This is typically only called during graceful shutdown.

Configuration

Default Values

  • flushInterval: 2000ms
  • maxBatchSize: 20 events
  • maxQueueSize: 1000 events
  • retryMaxAttempts: 5 attempts
  • retryInitialDelay: 1000ms
  • flushOnShutdown: true

Logging

The SDK uses a console wrapper with [Klime] prefix by default. You can provide a custom logger:

const client = new KlimeClient({
  writeKey: "your-write-key",
  logger: {
    debug: (msg, ...args) => myLogger.debug(msg, ...args),
    info: (msg, ...args) => myLogger.info(msg, ...args),
    warn: (msg, ...args) => myLogger.warn(msg, ...args),
    error: (msg, ...args) => myLogger.error(msg, ...args),
  },
});

Callbacks

const client = new KlimeClient({
  writeKey: "your-write-key",
  onError: (error, events) => {
    // Report to your error tracking service
    Sentry.captureException(error);
    console.error(`Failed to send ${events.length} events: ${error.message}`);
  },
  onSuccess: (response) => {
    console.log(`Sent ${response.accepted} events`);
  },
});

Error Handling

The SDK automatically handles:

  • Transient errors (429, 503, network failures): Retries with exponential backoff
  • Permanent errors (400, 401): Logs error and drops event
  • Rate limiting: Respects Retry-After header

For synchronous operations, use *Sync() methods which throw SendError on failure:

const { KlimeClient, SendError } = require("@klime/node");

try {
  const response = await client.trackSync("Event", {}, { userId: "user_123" });
} catch (error) {
  if (error instanceof SendError) {
    console.error(`Failed: ${error.message}, events: ${error.events.length}`);
  }
}