---
title: "Send events from Node.js and serverless functions"
description: "Install the server SDK, name the person on every call, and make sure events leave before the process stops."
canonical: "https://www.togohq.ai/docs/node"
applies_to: "@togohq/sdk-node 1.x"
updated: "2026-11-05T00:00:00Z"
---

# Send events from Node.js and serverless functions

Install the server SDK, name the person on every call, and make sure events leave before the process stops.

Use the server SDK where the thing happened on your server — a payment confirmed, a subscription
renewed, an invite accepted. In browser code, use [`@togohq/sdk-browser`](/docs/reference/sdk-browser).
Node 20 or later; zero runtime dependencies.

## Install and create one client

```bash
npm install @togohq/sdk-node
```

```ts
import { init } from '@togohq/sdk-node';

// One client per process, shared by every request.
export const analytics = init({ writeKey: process.env.TOGO_WRITE_KEY! });
```

Use the **test** key (`togo_pkt_…`) in development and staging and the **live** key
(`togo_pkl_…`) in production. They are the same public write keys the browser uses.

## Name the person on every call

A server handles many people at once, so the SDK remembers nobody. Every call says who it is
about:

```ts
analytics.track('order_paid', { plan: 'pro', seats: 5 }, { userId: user.id });
```

- **`userId`** — your id for the signed-in user. Use **the same one the browser passes to
  `identify()`**, or each user becomes two people.
- **`anonymousId`** — for a visitor who has not signed in: the browser's anonymous id, forwarded
  from the page (`analytics.getAnonymousId()` in the browser).
- **`sessionId`** — optional: the browser's session id, forwarded, so the event joins the visit it
  belongs to.

An event with neither `userId` nor `anonymousId` is dropped with an error in your logs, because
nobody could be credited with it. The SDK never makes an id up.
[Send events from your server for the right person](/docs/guides/server-events) shows how to
forward the browser's ids.

## Make sure events leave

Events are batched — 100 per request, or every 10 seconds — and retried with backoff when Togo is
unreachable. How you end depends on where the code runs:

| Where                                                  | Do this                                                   |
| ------------------------------------------------------ | --------------------------------------------------------- |
| Serverless function (Vercel, Netlify, Lambda, Workers) | Wrap the handler in `analytics.withFlush()`               |
| Long-running server                                    | `await analytics.shutdown()` on `SIGTERM`                 |
| Script or job                                          | Nothing — queued events are sent before the process exits |

**Serverless is the case that loses events.** The platform may freeze the function the moment the
response is sent, with the batch still in memory. `withFlush()` sends it before the handler
returns, on every return path and when it throws:

```ts
export const POST = analytics.withFlush(async (request: Request) => {
  // ...
  analytics.track('invite_accepted', {}, { userId });
  return Response.json({ ok: true });
});
```

Or call `await analytics.flush()` yourself before every `return`.

```ts
process.on('SIGTERM', async () => {
  await analytics.shutdown();
  process.exit(0);
});
```

Nothing on the client throws. A problem is logged as `[togo] …` and the event is retried or
dropped. At most 10,000 events wait in memory while Togo is unreachable; past that, new events are
dropped with an error.

## Profile properties

```ts
analytics.setPersonProperties(
  { set: { plan: 'pro' }, setOnce: { first_plan: 'free' } },
  { userId: user.id },
);
```

[Set person properties](/docs/guides/person-properties) explains which keys are kept.

## Check it works

Run the code path with the test key and confirm the events arrived:
[Check that events arrive](/docs/verify). All options are in the
[`@togohq/sdk-node` reference](/docs/reference/sdk-node).
