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 withexport 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.envfile that is not committed, loaded withnode --env-file=.env server.js(Node.js 20.6 or later) or thedotenvpackage.
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-nodeimport { 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.ContentExpected 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
| Symptom | Cause | Fix |
|---|---|---|
401 with code unregistered_sender | SA_API_KEY is empty, wrong or rotated | Copy the key again from Properties |
| 202 but the event is missing from reports | No source: "backend" and a script-like user agent, or environment set to something other than production | Send source: "backend"; leave out environment in production |
npm install specvista-analytics-node fails with E404 | Not published yet | Use step 2b |
| Events arrive twice after a retry | Ingest has no idempotency key yet | Do 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.