Install the SDK
Send events from the browser, from Node or Bun, or from any language over HTTP.
Let your coding agent do it. It detects your stack, sets up Datahop, identifies users, attaches accounts and adds your key events.
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
<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.
If you set up a custom domain, use it for both the script URL and host.
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
datahop.capture('checkout_started', { cart_value: 129, items: 2 })
datahop.register({ app_version: '4.2.0' }) // added to every later eventName 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
datahop.optOut()stops all capturing for this browser untiloptIn().persistence: 'memory'ininitstores 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 oflocalStorage.
All init options and methods are in the SDK reference.
Node / Bun
Use the server client for events your backend is the source of truth for, like payments, renewals and refunds. Download node.js into your project:
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)
Send a JSON batch of up to 500 events to POST /i/batch:
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" }
}]
}'{ "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.