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

# Browser SDK

> Send user events from your frontend with @dataframer/signals

`@dataframer/signals` is a small browser SDK. It sends user and product events to DataFramer, and it creates the journey id that ties those events to your AI traces.

## Install

```bash theme={null}
npm install @dataframer/signals
```

## Get a write key

In DataFramer, go to [**Settings → API Keys**](https://app.dataframer.ai/settings?tab=keys) and generate the **Signals SDK write key**. This key can only send events in, never read data out, so it is safe to ship in frontend code.

<img src="https://mintcdn.com/aimonlabsinc/BIX30HXoj6xW9wt-/images/user-signals/write-key-card.png?fit=max&auto=format&n=BIX30HXoj6xW9wt-&q=85&s=b9947cd27c9e43829e4088c16b5f57c5" alt="The Signals SDK write key card on the API Keys tab of Settings, with Copy and Rotate buttons" width="1520" height="428" data-path="images/user-signals/write-key-card.png" />

## Initialize once, at app start

```ts theme={null}
import { DataframerSignals } from '@dataframer/signals';

const signals = new DataframerSignals({
  apiBase: 'https://df-api.dataframer.ai',
  writeKey: 'YOUR_WRITE_KEY',
});
```

## Send events

```ts theme={null}
signals.identify('user@acme.com', { plan: 'pro' });   // sets the journey's user
signals.track('RecommendationCancelled', { itemId: 'abc', reason: 'irrelevant' });
signals.group('acme');                                // the user's account or org
signals.reset();                                      // on logout: forget the user and account
```

Sending is best-effort and never throws an error into your app. If DataFramer rejects an event (wrong write key, invalid payload, rate limit), the SDK logs a `console.warn` with the reason, and the rejected event shows up with that reason under **User Journeys → Live Events**. During setup, that page shows whether events were rejected or never sent.

## Journey ids

Every event carries a `journey_id`. There are two ways to get one:

* **Automatic (default):** the SDK creates an id for you. It resets after 30 minutes of inactivity (`journeyIdleMinutes`).
* **Your own id:** if you already have a natural id, such as an order id, conversation id or ticket id, you can use that:

```ts theme={null}
signals.startJourney('order-1234');   // pin your own id
signals.resetJourney();               // end it, start a fresh one
signals.journeyId;                    // read the current id
```

## Getting the journey id to your own backend

Your backend needs the journey id so it can stamp it onto your AI traces (see [Server Instrumentation](/docs/user-signals/server-instrumentation)). How it gets there depends on whether your frontend and your backend share an origin.

<Tabs>
  <Tab title="Same origin: nothing to do">
    If your frontend calls your backend on the origin it was served from (same scheme, host and port, for example a page on `https://app.acme.com` calling `https://app.acme.com/api`), the browser sends the SDK's journey cookie with every request, and `dataframer-journey` reads it. **Nothing to configure.** If you stamp traces without that library, see [Without this library](/docs/user-signals/server-instrumentation#without-this-library).
  </Tab>

  <Tab title="Different origins: two steps">
    If your backend is on another host, even a sibling subdomain such as `api.acme.com`, the browser does not send the cookie. Do two things:

    1. **Tell the SDK to forward the id as a header** on requests to your backend:

    ```ts theme={null}
    const signals = new DataframerSignals({
      apiBase: 'https://df-api.dataframer.ai',
      writeKey: 'YOUR_WRITE_KEY',
      propagateHeaderTo: ['https://api.acme.com'],   // your own backend, not DataFramer
    });
    ```

    The SDK patches `fetch` and `XMLHttpRequest`, so every request to that URL gets an `X-Journey-Id` header.

    2. **Allow that header in your backend's CORS configuration**, next to the headers you already allow such as `Authorization`. For example, with Django's `django-cors-headers`:

    ```python theme={null}
    from corsheaders.defaults import default_headers

    CORS_ALLOW_HEADERS = list(default_headers) + ['x-journey-id']
    ```

    Without this, the browser blocks the request in its CORS preflight, before it reaches your server.
  </Tab>
</Tabs>

## Content Security Policy (CSP)

If your site sets a `Content-Security-Policy`, allow your DataFramer API host in `connect-src`, or the browser blocks the SDK's requests:

```
connect-src 'self' https://df-api.dataframer.ai;
```

A blocked request produces no SDK error. It only shows as a CSP violation in the browser console (with `debug: true` the SDK also warns). If events do not arrive, check this first.

## Config reference

| Option | Default | What it does |
| - | - | - |
| `apiBase` | none | Your DataFramer API base URL |
| `writeKey` | none | Your Signals SDK write key |
| `journeyIdleMinutes` | `30` | Minutes of inactivity before an automatic journey id resets |
| `disableAutoRotation` | `false` | Never reset the automatic journey id |
| `propagateHeaderTo` | `[]` | Your backend URLs, or paths on your own origin such as `/api`, that get the `X-Journey-Id` header |
| `debug` | `false` | Log every delivery to the console, including network failures |

## Next steps

<Card title="Server Instrumentation" icon="server" href="/docs/user-signals/server-instrumentation">
  Stamp this same journey id onto your AI traces automatically.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.