# Install the SDK (/docs/getting-started/install)

<CopyPrompt preset="install">
  Let your coding agent do it. It detects your stack, sets up Datahop, identifies users, attaches accounts and adds your key events.
</CopyPrompt>

The SDK is served by Datahop itself. It isn't on npm. You need your **project token** (`dh_…`) from **Settings → Install**. The token is public and only sends events, so it's safe to ship. Never put a secret API key (`dh_sk_…`) in your app.

## Browser [#browser]

<Tabs items={['Script tag', 'ES module']}>
<Tab value="Script tag">

```html title="index.html"
<script src="https://app.datahop.dev/sdk/datahop.min.js"></script>
<script>
  datahop.init('dh_your_project_token', { host: 'https://app.datahop.dev' })
</script>
```

This defines a global `datahop`.

</Tab>
<Tab value="ES module">

```ts title="analytics.ts"
import datahop from 'https://app.datahop.dev/sdk/browser.js'

datahop.init('dh_your_project_token', { host: 'https://app.datahop.dev' })
export default datahop
```

If your bundler doesn't follow remote imports, download [`browser.js`](https://app.datahop.dev/sdk/browser.js) into your project and import it locally. It's safe to import during server-side rendering: every method does nothing without `window`.

</Tab>
</Tabs>

If you set up a [custom domain](/docs/getting-started/project-settings#custom-domain), use it for both the script URL and `host`.

### What it captures on its own [#what-it-captures-on-its-own]

- **Pageviews**, on load and on every client-side navigation. This works with React Router, Next.js, Vue Router, SvelteKit and any router that uses the History API. Hash-only navigation isn't tracked.
- **Sessions.** A session ends after 30 minutes without activity.
- **Context** on every event: URL, referrer, UTM parameters, screen size. Datahop adds browser, OS, device type and country.
- **First touch.** The first referrer and UTM campaign are saved on the person and never overwritten.

It doesn't capture clicks or form input. You send the events that matter yourself.

### Send events [#send-events]

```ts
datahop.capture('checkout_started', { cart_value: 129, items: 2 })
datahop.register({ app_version: '4.2.0' }) // added to every later event
```

Name events after what the user did, in `snake_case` and past tense: `signed_up`, `invite_sent`, `order_completed`. Put variants in properties, so use one `order_completed` with `payment: 'card'` instead of `card_order` and `paypal_order`. Send amounts as plain numbers (`"amount": 49`) with the currency in its own property, so you can sum and average them.

### Privacy and consent [#privacy-and-consent]

- `datahop.optOut()` stops all capturing for this browser until `optIn()`.
- `persistence: 'memory'` in `init` stores nothing in the browser. Every page load is a new anonymous visitor. Use it until the visitor consents.
- `persistence: 'cookie'` uses first-party cookies instead of `localStorage`.

All init options and methods are in the [SDK reference](/docs/reference/sdk).

## Node / Bun [#node--bun]

Use the server client for events your backend is the source of truth for, like payments, renewals and refunds. Download [`node.js`](https://app.datahop.dev/sdk/node.js) into your project:

```ts title="datahop.ts"
import { Datahop } from './vendor/datahop-node.js'

export const datahop = new Datahop({ token: process.env.DATAHOP_TOKEN!, host: 'https://app.datahop.dev' })

datahop.capture({
  distinctId: user.id, // the same id you pass to identify() in the browser
  event: 'plan_upgraded',
  properties: { from: 'free', to: 'pro', amount: 12, currency: 'USD' },
  groups: { company: org.id },
})
```

Events are sent in the background. In scripts, serverless functions and CLIs, call `await datahop.shutdown()` before the process exits.

To import history, pass a `timestamp` per event. If the import might run twice, also pass a stable `uuid` per event: Datahop stores an event only once per uuid.

## Any language (HTTP) [#any-language-http]

Send a JSON batch of up to 500 events to `POST /i/batch`:

```bash
curl -X POST https://app.datahop.dev/i/batch \
  -H 'content-type: application/json' \
  -d '{
    "token": "dh_your_project_token",
    "batch": [{
      "uuid": "'"$(uuidgen | tr A-Z a-z)"'",
      "event": "signed_up",
      "distinct_id": "user_123",
      "timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
      "properties": { "plan": "free" },
      "$set": { "email": "ada@example.com" }
    }]
  }'
```

```json
{ "ok": true, "accepted": 1 }
```

Generate a new `uuid` for each event and reuse it when you retry, so a retry never counts twice. Retry on `429`, `5xx` and network errors. Any other `4xx` means the payload is wrong, so don't retry it. All fields are in the [SDK reference](/docs/reference/sdk#http-endpoints).
