# 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](https://analytics.specvista.com/docs/get-a-project-key.md).
- 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

```bash
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

```bash
npm install specvista-analytics-node
```

```ts
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](https://analytics.specvista.com/docs/sdks.md).

## 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`:

```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:

```js
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](https://analytics.specvista.com/docs/api.md).

## 3. Test the key from a terminal

macOS and Linux:

```bash
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:

```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

| 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](https://analytics.specvista.com/docs/read-reports.md).
