# The Analytiics Node SDK

`@analytiics/sdk-node` sends authenticated server events, including revenue
and anything your backend knows that the page does not.

## Setup

```ts
import { createClient } from "@analytiics/sdk-node";

export const analytiics = createClient({
  site: "liinks",
  writeKey: process.env.ANALYTIICS_WRITE_KEY!,
  api: "https://in.analytiics.co",
});
```

Each project gets its own server write key. It is shown once, when issued, and
cannot be recovered afterwards — only its hash is stored. A leaked key can only
write to the project it was issued for, and revoking one takes no redeploy.

## Typed helpers in server handlers

Regenerate your helpers with `npx analytiics codegen`. Bind a typed helper to
each request's adapter after resolving its user:

```ts
import { toAnalytiicsClient } from "@analytiics/sdk-node";
import { createTrack } from "./analytics.gen";

// Inside the handler; analytiics is the SDK client from setup above.
const track = createTrack(toAnalytiicsClient(analytiics, { userId: user.id }));
track.pagePublished({ template: "grid", block_count: 7 });
await analytiics.flush();
```

Use the event names and properties in your own manifest. Each helper keeps its
own client, so overlapping requests retain the correct user. The legacy
`configure` function changes shared browser convenience calls and must never
hold a server request's identity. In serverless handlers, flush before returning.

## Attributing revenue to a channel

For Stripe, start with [Connect Stripe](/docs/stripe): paste a restricted key in
project settings to import payments, refunds and renewals. Add checkout metadata
for attribution. Do not also emit SDK revenue for the same Stripe payments.

The webhook approach below remains available for other providers and custom
billing, and for Stripe projects that deliberately use the SDK instead.

A Stripe webhook knows a customer and an amount. It has no cookie, no referrer
and no session — nothing that says the customer arrived from a search three days
ago. The join key is the **user id**, in two steps.

**In the browser, once the user is known:**

```ts
window.analytiics.identify(user.id);
```

Call it at signup and at login. That ties the anonymous visitor to a user id,
which is what lets a later server-side event find the session — and therefore
the source — that earned it.

**In the webhook:**

```ts
if (event.type === "checkout.session.completed") {
  const session = event.data.object;
  // Use a delivery client for this webhook, after verifying the provider signature.
  const delivery = createClient({ site: "liinks", writeKey: process.env.ANALYTIICS_WRITE_KEY! });
  delivery.revenue({
    name: "subscription_started",
    eventId: event.id,
    ts: new Date(event.created * 1000).toISOString(),
    userId: session.client_reference_id ?? undefined,
    revenueCents: session.amount_total,
    currency: session.currency.toUpperCase(),
  });
  try {
    await delivery.flush({ throwOnError: true });
  } catch {
    return new Response("Analytics delivery failed; retry this webhook", { status: 503 });
  }
}
```

Currency is required with any amount. Revenue is grouped by currency on the
dashboard and never summed across currencies. Keep the provider event id and
original timestamp unchanged when a webhook retries. Strict flush reports a
delivery failure so the handler can return a retryable response; the default
flush remains best-effort. Use a separate SDK client per webhook delivery.

## Why not from the browser

The public collector ships to every visitor, so it is unauthenticated by
necessity — and therefore ignores `revenue_cents` outright. If it did not,
anyone who could read your page source could post fake sales into your
dashboard. This is the whole reason the server path exists.
