# Web SDK (browser)

URL: https://traqio.app/docs/sdks/web
Last updated: 2026-10-16

Add Traqio to a website: @traqio/web keeps the ad click id from the landing URL to the conversion in first-party storage. React, Vue, Angular or plain HTML.

`@traqio/web` is the Traqio SDK for websites and web apps. When a visitor arrives from a Traqio tracking link, the landing URL carries the ad click id — the **trackID** — as `traq_click`. The SDK reads it, keeps it in **first-party storage** (localStorage, plus a cookie on your own domain) and hands it back when the visitor converts, so the conversion can be tied to its click [deterministically](https://traqio.app/docs/concepts/attribution), not by guesswork. It also records web events and visits, with consent and erasure. No fingerprinting, no third-party cookie, no advertising id; about 7 KB gzipped, one package for React, Vue, Angular and plain HTML.

## Requirements

- Any evergreen browser (the SDK uses `fetch` and `localStorage`).
- A **web key** for the site. The app key of a mobile app is refused here, and the reverse is true.
- Links that land on your site: a [tracking link](https://traqio.app/docs/concepts/tracking-links) whose destination is a page of the site, so the trackID arrives in the URL.

## Set up

### Create a web key

In the console, open **Integrations → SDK** for the site and create a web key. List every origin that runs the SDK — scheme and host, no path: `https://example.com`, `https://www.example.com`, `https://app.example.com`. There is no wildcard. A request from any other origin, or with no `Origin` header at all (curl, a server), is refused with a 403.

### Install the package

```bash
npm install @traqio/web
```

Or, without a build step, one tag on every page — it initialises itself and exposes `window.Traqio`:

```xml
<script src="https://cdn.jsdelivr.net/npm/@traqio/web@0.1/build/traqio.min.js"
        data-sdk-key="YOUR_WEB_KEY" async></script>
```

### Initialise on every page

React / Next.js:
```tsx
// app/traqio.tsx — mounted once, in the root layout
"use client";
import { useEffect } from "react";
import { Traqio } from "@traqio/web";

export function TraqioInit(): null {
  useEffect(() => {
    void Traqio.init({ sdkKey: process.env.NEXT_PUBLIC_TRAQIO_WEB_KEY! });
  }, []);
  return null;
}
```

Vue / Nuxt:
```ts
// main.ts (Vue) — or plugins/traqio.client.ts in Nuxt
import { createProduct } from "vue";
import { Traqio } from "@traqio/web";
import App from "./App.vue";

void Traqio.init({ sdkKey: import.meta.env.VITE_TRAQIO_WEB_KEY });
createProduct(App).mount("#app");
```

Angular:
```ts
// app.config.ts
import { ApplicationConfig, PLATFORM_ID, inject, provideAppInitializer } from "@angular/core";
import { isPlatformBrowser } from "@angular/common";
import { Traqio } from "@traqio/web";

export const appConfig: ApplicationConfig = {
  providers: [
    provideAppInitializer(() => {
      if (isPlatformBrowser(inject(PLATFORM_ID))) void Traqio.init({ sdkKey: "YOUR_WEB_KEY" });
    }),
  ],
};
```

JavaScript:
```ts
import { Traqio } from "@traqio/web";

Traqio.init({ sdkKey: "YOUR_WEB_KEY" }); // tq_web_… — a WEB key, not the app key
```

Once, as early as possible. `init` reads the trackID from the current URL before anything else, so a visitor who leaves at once still keeps it. It is safe during server-side rendering: on a server it does nothing.

### Keep the trackID with the user

The conversion that counts — a sign-up, a subscription, a purchase — usually happens on your backend, where the user id is known. The SDK writes the cookie `traq_click` on your registrable domain (`example.com`), so your backend receives it with the sign-up request itself:

```ts
// Node / Express — the browser sends the cookie with the sign-up request itself
app.post("/signup", async (req, res) => {
  const trackId = req.cookies.traq_click ?? req.body.traq_click ?? null;
  const user = await users.create({ email: req.body.email, traqClick: trackId });
  // …
});
```

If your backend is on another domain, send it explicitly — `Traqio.getTrackId()` returns it, or null when the visitor did not come from an ad:

```xml
<form id="signup" action="/signup" method="post">
  <input type="hidden" name="traq_click" />
  …
</form>
<script>
  // Filled at submit time, so it works whenever the SDK finished loading.
  document.getElementById("signup").addEventListener("submit", (e) => {
    e.target.elements.traq_click.value = window.Traqio?.getTrackId() ?? "";
  });
</script>
```

Your backend then reports the sign-up with the trackID through a server SDK — [Node.js](https://traqio.app/docs/sdks/node), [PHP / Laravel](https://traqio.app/docs/sdks/php) or [Python](https://traqio.app/docs/sdks/python) — which ties it to the click.

### Track events and set the user id

```ts
Traqio.setUserId(user.id); // after sign-in

Traqio.track("signup");
Traqio.track("purchase", { value: 49, currency: "EUR", transactionId: payment.id, store: "stripe" });
```

Events are batched, retried, kept across page loads, and sent when the tab is hidden or the page is left. A tracked event is a [conversion](https://traqio.app/docs/concepts/events-and-revenue): track the steps that matter, not every page view.

## What it does today, and what it does not

|  | Today |
| --- | --- |
| Read and keep the trackID (localStorage + first-party cookie) | Yes |
| Share it across subdomains of the same site | Yes, through the cookie |
| Events with value, currency and payment id | Yes — recorded, not attributed to a campaign |
| Visits (retention) | Yes |
| Consent, storage only after consent, erasure | Yes |
| Tie a sign-up to its click | Yes — your backend, through a server SDK |
| Show web conversions in the console's reports | Yes — per campaign, beside installs, once your backend ties the sign-up |
| Send web events to Meta or TikTok | Not yet |

**The browser SDK never sends the trackID itself.** A key anyone can read in your page must not be able to credit a campaign, so the tie between a conversion and its click is made by your backend, with a secret server key, through a [server SDK](https://traqio.app/docs/concepts/web-attribution#server). Events the browser sends itself are recorded without a campaign, and no web event is forwarded to ad networks yet.

> **Web events never touch your app's numbers** An event sent with a web key is never attached to an app install, even when its user id matches one: your mobile installs, revenue and ROAS stay exactly what the apps report.

## Consent

```ts
Traqio.init({ sdkKey: "YOUR_WEB_KEY", waitForConsent: true });

// when your consent banner gets an answer
consentBanner.onAnswer((granted) => Traqio.setConsent(granted));
```

With `waitForConsent`, nothing is written to the device — no localStorage, no cookie — until `setConsent(true)`; the SDK still works for the current page, in memory. `setConsent(true)` then stores what the page kept, trackID included; `setConsent(false)` removes everything the SDK stored. The same answer governs whether data about this visitor may be shared with ad networks — see [privacy](https://traqio.app/docs/concepts/privacy).

## API reference

WebInitOptions:
```ts
interface WebInitOptions {
  sdkKey: string;           // the site's web key (tq_web_…)
  endpoint?: string;        // default "https://api.traqio.app"
  userId?: string;          // your user id, once known
  consent?: boolean;        // ad-sharing consent, if already known
  waitForConsent?: boolean; // store nothing until setConsent(true) — default false
  trackIdDays?: number;     // how long the trackID is kept — default 30
  cookies?: boolean;        // false = localStorage only — default true
}
```

| Method | What it does |
| --- | --- |
| init(options) | Reads the trackID from the URL, then starts the SDK. Idempotent; a no-op on a server. |
| track(name, props?) | Queues an event: value, currency, transactionId, store. |
| setUserId(id) | Your user id, after sign-in. |
| getTrackId() | The kept trackID, or null when there is none or it expired. |
| setConsent(granted) | Ad-sharing consent; with waitForConsent, also whether anything may be stored. |
| forgetMe() | Erasure: the server deletes what it holds about this browser and user, the SDK goes quiet and forgets the trackID. |
| flush() | Sends the queue now. Already wired to tab hide and page exit. |

## Limits

- **Safari** keeps storage written by JavaScript for 7 days on sites it classifies as tracking: the trackID can expire sooner there than `trackIdDays` says.
- A click in an app's in-app browser followed by a conversion in another browser, without signing in, carries no trackID: that conversion is shown as unattributed, never guessed.

> **Proof so far** The package is unit-tested against a simulated browser and type-checked and built in CI; it has not yet run on a production site.

## Frequently asked questions

**What is the trackID?**
The click id of the ad the visitor clicked. A Traqio tracking link adds it to the landing URL as the traq_click parameter; @traqio/web reads it and keeps it in first-party storage (localStorage and a cookie on your own domain), so the conversion can later be tied to that exact click instead of being guessed.

**Is the web key a secret?**
No. It sits in your page, so anyone can read it. It is protected by its list of allowed origins: the Traqio API accepts it only from the origins you declared, checked on the Origin header the browser sets itself. Because it is public, a web key cannot attribute a conversion to a click — that takes a secret server key, used by your backend.

**Does @traqio/web work with server-side rendering?**
Yes. Nothing touches window or document when the module is imported, and init() does nothing on a server. Call it from code that runs in the browser: a client component, a .client plugin, or an initializer guarded by isPlatformBrowser.

**How long is the trackID kept?**
30 days by default (the trackIdDays option). The server applies your attribution window anyway, so keeping it longer is harmless. Safari caps storage written by JavaScript at 7 days for sites that use it to track, so expect 7 days there.
