# Script reference

~~~html
<script defer data-website-id="tb_YOUR_SITE_ID" data-domain="example.com" src="https://twelvebit.app/js/script.js"></script>
~~~

The script sends a page view on load and on each client-side navigation. It also sends goals, identify calls, clicks on outbound links and one ping when the page is hidden (for time on site). It sends nothing else.

## Attributes

| Attribute | Default | What it does |
|---|---|---|
| `data-website-id` | required | The public id of the site (`tb_…`). Without it, the script logs a warning and stops. |
| `data-domain` | none | Your root domain, for example `example.com`. When the page host ends with this value, the cookies are set on `.example.com`, so `www.`, `app.` and `blog.` share one visitor. Without it, cookies belong to the current host only. |
| `data-api` | `/api/e` on the script's origin | The URL that receives events. Use it when you [proxy](/docs/proxy) the script, for example `data-api="/tb/e"`. Absolute and relative URLs work. |
| `data-cookieless` | `false` | `"true"` sets no cookies. The server makes a visitor id from a daily rotating hash. See [Privacy](/docs/privacy#cookieless-mode). |
| `data-allow-localhost` | `false` | `"true"` tracks on `localhost`, `127.*`, `[::1]`, `*.local` and `*.test`. Use it only for a local test. |
| `data-hash` | `false` | `"true"` counts hash changes (`/#/pricing`) as page views, for hash-routed apps. Without it, the hash is ignored. |
| `data-disable-outbound` | `false` | `"true"` stops the tracking of clicks on links to other hosts. |
| `data-allowed-hostnames` | none | Comma list of your other root domains, for example `"myshop.com,docs.io"`. Links to them carry the visitor, so one person stays one visitor across your domains. Add the domains in Settings → General → Other domains too. |
| `data-disable-payments` | `false` | `"true"` turns off payment detection from the success-page URL (see [Revenue](/docs/revenue)). |
| `data-allow-iframe` | `false` | `"true"` tracks when the page runs inside an iframe. |
| `data-debug` | `false` | `"true"` logs to the console why the script does not track (`[twelvebit] not tracking: localhost`). |

## What the script stores

| Name | Where | Lifetime | Content |
|---|---|---|---|
| `_tb_vid` | First-party cookie | 1 year | Random visitor id |
| `_tb_sid` | First-party cookie | 30 minutes, renewed with each event | Random session (visit) id |
| `twelvebit_ignore` | `localStorage` | Until you remove it | `"true"` excludes this browser |

Cookies use `SameSite=Lax` and `Secure` on HTTPS. They are not `HttpOnly`, because your server reads them to [attribute revenue](/docs/revenue). In cookieless mode the script sets no cookies.

## When the script does not track

- The host is `localhost`, `127.*`, `[::1]`, or ends with `.local` or `.test` (unless `data-allow-localhost="true"`).
- The page is a `file:` URL.
- The browser is automated (`navigator.webdriver`, PhantomJS, Nightmare, Cypress, Selenium) or the user agent is a headless browser or an HTTP library.
- The page runs inside an iframe (unless `data-allow-iframe="true"`).
- `localStorage.twelvebit_ignore` is `"true"`.

In these cases `window.twelvebit` becomes a function that does nothing, so your calls never throw. On the server, twelvebit also drops requests from known bots and excluded IPs, paths, hostnames and countries.

## JavaScript API

~~~js
window.twelvebit("signup", { plan: "pro" });            // goal with props
window.twelvebit("identify", { user_id: "u_123", email: "ada@example.com", name: "Ada" });
window.twelvebit("payment", { email: "ada@example.com" }); // on the success page: links the payment to this visitor
window.twelvebit("pageview");                            // count the current URL now
~~~

- Goal names are stored as lowercase snake_case, at most 64 characters ("Sign Up!" becomes `sign_up`). A name with no letters is dropped.
- Props: up to 30 keys (64 characters each). Strings are cut to 255 characters. Objects are sent as JSON strings. `null` and `undefined` are dropped.
- Use optional chaining (`window.twelvebit?.(…)`) when the script can be missing, for example in tests.

## Queue calls before the script loads

The script loads with `defer`. Calls made before it runs are lost, unless you add this stub before the script tag. The script replays the queue when it loads.

~~~html
<script>window.twelvebit = window.twelvebit || function () { (window.twelvebit.q = window.twelvebit.q || []).push(arguments); };</script>
<script defer data-website-id="tb_YOUR_SITE_ID" data-domain="example.com" src="https://twelvebit.app/js/script.js"></script>
~~~

## HTML attributes

~~~html
<button data-tb-goal="signup" data-tb-goal-plan="pro" data-tb-goal-billing-cycle="yearly">Start</button>
~~~

A click on an element with `data-tb-goal` (or on anything inside it), or Enter or Space when it has focus, sends that goal. Each `data-tb-goal-<name>` attribute becomes a prop; hyphens become underscores (`billing_cycle`). See [Goals](/docs/goals).

## Scroll goals

~~~html
<section data-tb-scroll="saw_pricing" data-tb-scroll-threshold="0.5" data-tb-scroll-delay="1000" data-tb-scroll-plan="pro">…</section>
~~~

The goal fires once per page when the element is visible: `threshold` is the visible share (0 to 1, default 0.5) and `delay` the time in milliseconds it must stay visible (default 0). Other `data-tb-scroll-<name>` attributes become props, and `scroll_percentage` is added. Elements added later, for example by a router, are picked up.

## Payments from the success page

When a page URL has `session_id=cs_…` (Stripe Checkout and Payment Links) or `checkout_id` (Polar), the script sends a `payment` event with that id once per tab. When the payment arrives from the provider, twelvebit links it to this visit, even when the checkout had no metadata. Turn it off with `data-disable-payments="true"`.

## Cross-domain

With `data-allowed-hostnames`, links to those domains get `_tb_vid` and `_tb_sid` parameters. The script on the other domain keeps the same visitor and session only when the visitor came from one of the allowed domains (by the referrer), and it removes the parameters from the address bar. For Lemon Squeezy and Paddle, pass the visitor in the checkout data instead (see [Revenue](/docs/revenue)).

## Page views and navigation

- The first page view carries `document.referrer`. Later page views in the same page load do not.
- The script wraps `history.pushState` and `history.replaceState` and listens to `popstate`, so client-side routers work without extra code.
- A page view is sent only when the URL changes. The query string counts; the hash counts only with `data-hash="true"`.
- Prerendered pages count when they become visible.
- UTM parameters (`utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`) and `ref`, `via` or `source` in the URL set the source of the visit.

## Outbound links

A click on an `http` or `https` link to another host sends an `outbound` event with the link URL. Links to a subdomain are other hosts too. The event uses `sendBeacon`, so it is not lost when the page unloads. Turn it off with `data-disable-outbound="true"`.

## Time on site

When the page is hidden (tab change, close), the script sends one small ping. It moves the end time of the visit, so time on site is correct for one-page visits.

## Transport

Events go to `data-api` (default `https://twelvebit.app/api/e`) as a `POST` with a `text/plain` JSON body, so there is no CORS preflight. The request uses `keepalive` and sends no cookies to twelvebit. Bodies larger than 16 KB are rejected.

---

Source: https://twelvebit.app/docs/script · All docs: https://twelvebit.app/llms.txt
