> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formo.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Server

> Send events and identify users from your backend with Formo server-side SDKs for Node.js, Python, and other languages. Stateless and wallet-native.

The Formo server-side SDKs allow you to track actions and identify users from your backend applications.

Server-Side SDKs are stateless - they don't store or remember any values between calls. Every call must explicitly provide wallet address values for identification.

The Server-Side SDK is [open source](https://github.com/getformo/sdk-node) and implements the standard [Events API](/data/events/overview#events-api).

## Installation

Install the `@formo/analytics-node` package:

```bash theme={null}
npm install @formo/analytics-node
```

## Quick Start

Initialize the SDK with your project's write key:

```typescript theme={null}
import { FormoAnalytics } from "@formo/analytics-node";

const analytics = new FormoAnalytics("<YOUR_WRITE_KEY>");
```

## Identify users

Call [`identify()`](/data/events/identify) when a user signs in or connects their wallet to associate them with their actions.

```typescript theme={null}
import { v4 as uuid } from "uuid";

await analytics.identify({
  address: "0x9798d87366bdfc5d70b300abdffc4f9e95369b3d", // required: wallet address
  anonymousId: uuid(), // optional: auto-generated if not provided
  properties: {
    provider_name: "MetaMask",
    rdns: "io.metamask",
  },
});
```

## Track events

To track custom events (actions, conversions, or backend states) use the [`track`](/data/events/track) function.

```typescript theme={null}
await analytics.track({
  address: "0x9798d87366bdfc5d70b300abdffc4f9e95369b3d", // optional: wallet address
  anonymousId: uuid(), // optional: auto-generated if not provided
  event: "Purchase Completed", // required: event name
  properties: {
    order_id: "123",
    revenue: 99.99,
    currency: "USD",
  },
});
```

You can [track volume, revenue, and points](/data/events/track#tracking-volume-revenue-points) in custom events.

## Configuration

### Options

Customize the SDK behavior during initialization by passing an optional configuration object:

| Option          | Type     | Default        | Description                                           |
| :-------------- | :------- | :------------- | :---------------------------------------------------- |
| `flushAt`       | `number` | `20` events    | Flush the queue once it contains N events.            |
| `flushInterval` | `number` | `30000` ms     | Flush the queue every N milliseconds.                 |
| `maxQueueSize`  | `number` | `500000` bytes | Flush when the queue exceeds N bytes (approx. 500KB). |
| `retryCount`    | `number` | `3` retries    | Number of times to retry failed requests.             |

```typescript theme={null}
const analytics = new FormoAnalytics("<YOUR_WRITE_KEY>", {
  flushAt: 20,
  flushInterval: 30000,
  maxQueueSize: 500000,
  retryCount: 3,
});
```

### Manual flushing

To ensure all pending events are sent before a process exits (useful for serverless functions), call `flush()`:

```typescript theme={null}
await analytics.flush();
```

## Data validation

The SDK throws a `ValidationError` when required fields are missing or invalid:

```typescript theme={null}
import { FormoAnalytics, ValidationError } from "@formo/analytics-node";

try {
  await analytics.track({
    event: "", // empty event name will fail validation
  });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error(`Validation failed: ${error.field} - ${error.reason}`);
  }
}
```

## Verification

To verify that your integration is working correctly:

1. **Send a test event**: Trigger an action in your application that calls `track()` or `identify()`.
2. **Check the Dashboard**: Go to the **Activity** page in the Formo dashboard.
3. **Confirm Ingestion**: Your events should appear in the activity stream within a few seconds after a flush occurs.

If you don't see events, ensure that you are calling `await analytics.flush()` if your script exits immediately, and check for any `ValidationError` in your server logs.

## Graceful shutdown

The SDK automatically attempts to flush pending events before the process closes by listening to the following Node.js process events:

* `beforeExit`: Flushes events when the process is about to exit naturally.
* `SIGTERM`: Flushes events when the process receives a termination signal (common in Docker/Kubernetes).
* `SIGINT`: Flushes events when the process is interrupted (e.g., `Ctrl+C`).

### Important caveats

* **Manual `process.exit()`**: If your code calls `process.exit()` directly, the `beforeExit` event will not fire, and the queue will not be flushed.
* **Serverless Environments**: In platforms like AWS Lambda or Vercel, the environment may be frozen immediately after your handler returns. **You must call `await analytics.flush()` manually** at the end of your function.
* **Forceful Shutdowns**: A `SIGKILL` (`kill -9`) or sudden crash will prevent any automatic flushing.

For the most reliable delivery in mission-critical applications or serverless functions, we recommend calling `await analytics.flush()` explicitly:

```typescript theme={null}
process.on("SIGTERM", async () => {
  await analytics.flush();
  process.exit(0);
});
```
