# Installation

URL: https://supapulse.app/docs/revenue/installation

Set up revenue tracking. Identify customers in the browser, connect Stripe or get the secret token, and choose between the HTTP API and the @supapulse/revenue SDK.

## 1. Identify customers in the browser [#1-identify-customers-in-the-browser]

Install the [browser tracker](https://supapulse.app/docs/quickstart) and call `identify()` once the user is known, after login or signup:

```ts
supapulse.identify(user.id); // window.supapulse with the script tag
```

In React, use [`useIdentify()`](https://supapulse.app/docs/react/hooks#useidentify). `identify()` never counts toward your event usage.

## 2. Pick how payments arrive [#2-pick-how-payments-arrive]

- **Stripe:** open the site's settings, find the Revenue section, and follow [the Stripe guide](https://supapulse.app/docs/guides/stripe). You don't need the secret token.
- **Anything else:** continue below.

## 3. Get the secret token [#3-get-the-secret-token]

Open the site's settings in the dashboard and regenerate the secret token. It's shown once, so copy it then. It starts with `sec_`.

Regenerating replaces the old token right away. If a server is already using it, update that server at the same time.

```bash title=".env"
SUPAPULSE_SECRET_TOKEN=sec_your_secret_token
```

Don't prefix it with `NEXT_PUBLIC_`, `VITE_` or anything else that bundles it into browser code.

## 4. Send revenue [#4-send-revenue]

### @supapulse/revenue SDK [#supapulserevenue-sdk]

```bash
npm install @supapulse/revenue
```

```ts title="lib/supapulse.ts"
import { Revenue } from '@supapulse/revenue';

export const revenue = new Revenue({ apiToken: process.env.SUPAPULSE_SECRET_TOKEN! });

await revenue.track({
  customer_id: 'user_123',
  amount: 49.9,
  currency: 'USD',
  transaction_id: 'in_1Q8x…',
  event_type: 'renewal',
});
```

`track()` validates its input, retries network errors and `429` responses, and throws straight away on any other `4xx`. It warns in the console if it's loaded in a browser. The full surface is in the [Revenue API reference](https://supapulse.app/docs/revenue/api-reference#the-supapulserevenue-sdk).

### HTTP API [#http-api]

No package needed. Any language that can make an HTTPS request works:

```ts title="lib/supapulse.ts"
type Revenue = {
  customerId: string;
  amount: number;
  transactionId: string;
  eventType?: 'purchase' | 'subscription' | 'renewal' | 'upgrade' | 'downgrade' | 'churn' | 'refund';
  currency?: string;
  visitorFingerprint?: string;
  sessionId?: string;
  paymentProcessor?: string;
  subscriptionId?: string;
  mrrImpact?: number;
  occurredAt?: string;
};

export async function trackRevenue(revenue: Revenue): Promise<void> {
  const response = await fetch('https://supapulse.app/api/ingest', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Token': process.env.SUPAPULSE_SECRET_TOKEN!,
    },
    body: JSON.stringify({
      type: 'revenue',
      data: {
        customer_id: revenue.customerId,
        amount: revenue.amount,
        transaction_id: revenue.transactionId,
        event_type: revenue.eventType,
        currency: revenue.currency ?? 'USD',
        visitor_fingerprint: revenue.visitorFingerprint,
        session_id: revenue.sessionId,
        payment_processor: revenue.paymentProcessor,
        subscription_id: revenue.subscriptionId,
        mrr_impact: revenue.mrrImpact,
        occurred_at: revenue.occurredAt,
      },
    }),
  });

  if (!response.ok) {
    throw new Error(`Supapulse ingest failed: ${response.status} ${await response.text()}`);
  }
}
```

`JSON.stringify` drops `undefined` fields, so optional values can be left out. The rest of these docs use this `trackRevenue()` helper.

## 5. Check it [#5-check-it]

Send a test payment through your provider's test mode, then open the site's revenue view. The payment shows up once the worker has processed it, usually within seconds.

If nothing appears:

- `401 Invalid API token`: the token is wrong or was regenerated.
- `402`: the site's team has no active plan. The body's `code` is `plan_inactive`.
- `422`: the body is missing `customer_id`, `amount` or `transaction_id`, or a field is invalid. The response lists each field. A public token also gets `422`, because it can't send `revenue`.
- `202` but no new revenue: a payment with the same `transaction_id` and `event_type` was already stored.
