Skip to content

Node SDK

@syntarie/tracking-node is the server-side counterpart to the browser SDK. It is intentionally narrower than the browser entry: there is no auto-capture, no DOM, no offline queue, and no anonymous-id generation. Server-side code already knows who the user is — the SDK refuses to invent an anonymous id.

Terminal window
npm install @syntarie/tracking-node

Requires Node 20 or newer. ESM-only.

import { init } from '@syntarie/tracking-node';
init({
siteId: 'site_marketing',
host: 'https://collect.example.com',
// optional:
batchSize: 20,
flushIntervalMs: 5_000,
maxBufferedEvents: 10_000,
});
FieldTypeDefaultNotes
siteIdstringrequiredThrows on empty.
hoststringrequiredThrows on empty.
batchSizenumber20Throws on non-positive.
flushIntervalMsnumber5_000Throws on non-positive.
maxBufferedEventsnumber10_000Throws on non-positive. Older events are oldest-dropped above this cap.
transportTransportOptionsInject a custom fetch (e.g. corp proxy).

Calling init more than once throws. The browser SDK is permissive here because hot-reloading bundles re-execute module top-level; in a long-lived Node process double-init is almost always a bug.

import { identify } from '@syntarie/tracking-node';
identify('user_42', { plan: 'pro' });

Sets the process-wide identified user. Every subsequent track() call that does not supply a userId attribute is attributed to this user. If a different prior user was set, the SDK emits a merge event before the new identify.

import { track } from '@syntarie/tracking-node';
// Uses process-wide identity:
track('subscription_renewed', { plan: 'pro' });
// Or override per call:
track(
'subscription_renewed',
{ plan: 'pro' },
{ userId: 'user_42', timestamp: Date.now() },
);

Resolution order for the user: options.userId → process-wide identity → throw. The SDK never silently attributes server events to “anon” — that would surface as “where are my events?” debugging weeks later.

import { flush } from '@syntarie/tracking-node';
process.on('SIGTERM', async () => {
await flush();
process.exit(0);
});

Drains the in-memory buffer. Resolves once the underlying request settles. No-op when init has not run.

import { init, type InitOptions } from '@syntarie/tracking-node';
const options: InitOptions = {
siteId: 'site_marketing',
host: 'https://collect.example.com',
transport: {
fetch: customFetch, // e.g. wrapped with a corp HTTP agent
},
};
init(options);

TransportOptions is part of the public contract — its precise shape is documented in the source and follows the same semver guarantees as the rest of the surface.

  • No anonymous-id generation. Use the browser SDK on the client side and identify() for known server-side users.
  • No autocapture. There are no pageview events from a server runtime.
  • No durable on-disk queue. The buffer is in-memory and bounded by maxBufferedEvents. If you need durability across crashes, persist events to your own queue and emit them on resume.
  • No retry orchestration on the public surface. The transport handles transient failures internally; permanent failures (4xx with no recovery) are dropped after logging.