Add tracking to a Node.js server

Send events such as signup.completed or payment.succeeded from a server, worker or API built on Node.js, NestJS or Express.

Prerequisites

  • Node.js 18 or later.
  • A backend property and its key. See Get a project key.
  • The key in an environment variable named SA_API_KEY. Never commit it. For a quick test in a terminal, set it with export SA_API_KEY=<project key> on macOS and Linux, or $env:SA_API_KEY = "<project key>" in Windows PowerShell. In production, use your host's secret settings, or a .env file that is not committed, loaded with node --env-file=.env server.js (Node.js 20.6 or later) or the dotenv package.

1. Check whether the SDK is on npm

npm view specvista-analytics-node version
  • If it prints a version, follow step 2a.
  • If it fails with E404, the SDK is not published yet. Follow step 2b, which needs no package.

2a. With the SDK

npm install specvista-analytics-node
import { initAnalytics, track, identify } from "specvista-analytics-node";

initAnalytics({ apiKey: process.env.SA_API_KEY! });

track("signup.completed", { userId: user.id, plan: "pro" });
identify(user.id, { email: user.email, name: user.name });

track returns straight away and swallows network errors and timeouts. It drops event names that do not match ^[a-z][a-z0-9._:-]{1,79}$ (case-insensitive) and every call made before initAnalytics. NestJS can construct new AnalyticsService(process.env.SA_API_KEY!) as a provider instead. All options are in the SDK reference.

2b. With plain HTTP

Add this helper, which uses the built-in fetch and has the same call shape as the SDK's track. Save it as analytics.js:

const SA_URL = "https://analyticsapi.specvista.com/api/v1";

function track(event, properties = {}) {
  const apiKey = process.env.SA_API_KEY;
  if (!apiKey) return;
  const { userId, sessionId, ...rest } = properties;
  fetch(`${SA_URL}/ingest`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Api-Key": apiKey },
    body: JSON.stringify({ event, source: "backend", userId, sessionId, properties: rest }),
    signal: AbortSignal.timeout(5000),
  }).catch(() => {});
}

module.exports = { track };

That is CommonJS, the default for Express apps. In an ES module project ("type": "module" in package.json, or TypeScript with ES modules), write export function track and drop the module.exports line; in TypeScript, type the parameters as event: string, properties: Record<string, unknown> = {}. Call it like the SDK:

const { track } = require("./analytics");

track("signup.completed", { userId: String(user.id), plan: "pro" });

Always send source: "backend". Without it, requests from HTTP clients whose user agent looks like a script (curl, wget, python-requests, Go and others) are stored as bot traffic and left out of reports.

To attach an email or name to a user, POST /ingest/identify with {"userId":"user_123","email":"person@example.com","name":"Ada"}. To send up to 100 events in one request, POST /ingest/batch with a JSON array. Fields are listed in the API reference.

3. Test the key from a terminal

macOS and Linux:

curl -i https://analyticsapi.specvista.com/api/v1/ingest \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $SA_API_KEY" \
  -d '{"event":"setup.checked","source":"backend","properties":{"from":"terminal"}}'

Windows PowerShell:

$response = Invoke-WebRequest -Method Post -Uri https://analyticsapi.specvista.com/api/v1/ingest -ContentType "application/json" -Headers @{ "X-Api-Key" = $env:SA_API_KEY } -Body '{"event":"setup.checked","source":"backend","properties":{"from":"terminal"}}'
$response.StatusCode
$response.Content

Expected result: status 202 and the body {"ok":true}. This stores one real event, so send it to the property you are setting up, not to someone else's.

Deleted accounts

When a user deletes their account, send account.deleted with their userId from a backend key. Their identity is erased and their events are kept without the user id or personal properties. You can also call POST /forget with {"userId":"user_123"}. Both are permanent, so only send them for a real deletion.

Common errors

SymptomCauseFix
401 with code unregistered_senderSA_API_KEY is empty, wrong or rotatedCopy the key again from Properties
202 but the event is missing from reportsNo source: "backend" and a script-like user agent, or environment set to something other than productionSend source: "backend"; leave out environment in production
npm install specvista-analytics-node fails with E404Not published yetUse step 2b
Events arrive twice after a retryIngest has no idempotency key yetDo not retry a request that returned 202

Confirm success

GET /api/v1/analytics/{projectId}/live-events with a bearer token lists up to 20 events from the last hour, newest first, including setup.checked. On a busy property it can drop out of that list within seconds; check GET /api/v1/analytics/{projectId}/event-names?period=24h instead. See Read reports.