Skip to content
whovisited.

Security

Is a website analytics site ID a secret?

Understand what a public tracker site ID can do, how origin checks and short-lived tokens protect event ingestion, and where browser analytics still has limits.

A browser tracker ID is configuration, not a password

A website analytics snippet usually contains an ID that tells the tracker which site configuration to use. Anyone who can load your page can inspect its HTML or browser network requests and see that ID. For example, a WhoVisited installation looks like this:

html
<script async src="https://whovisited.net/tracker.js" data-site-id="YOUR_SITE_ID"></script>

That visibility is expected. A public site ID selects the site; it is not a dashboard password and does not contain the workspace's signing secret. Hiding it with minification, splitting it across JavaScript strings, or loading it from a client-side environment variable does not make it secret. In Next.js, values with a `NEXT_PUBLIC_` prefix are included in browser code, so never use that prefix for a private signing key.

Treat the ID like a public installation setting. Keep server credentials and user authentication separate, and avoid putting private data into event properties just because the tracker has an ID.

What WhoVisited checks before accepting an event

When the browser starts collecting, the tracker sends the site ID and the page's request origin to the bootstrap endpoint. WhoVisited checks the allowed-origin list, site and workspace collection status, and event quota before returning a signed ingestion token. That token is short-lived—five minutes—and carries the site, workspace, and origin claims. The tracker then sends event batches with the token as a bearer credential.

For an event request, the server verifies the token signature, issuer, audience and expiry. It checks that the token origin matches the request origin and that each event URL uses that origin. The site and workspace collection checks run again. The event payload also has a schema: batches contain at most 20 events, event names and properties have length and type limits, and custom events must be enabled.

These controls mean the public site ID is not enough to read reports or call the dashboard. They also help reject ordinary browser collection from unlisted origins and limit damage from accidental misuse. They are layered checks, not a guarantee that every browser event came from a real person or a completed business action.

An origin allowlist is not bot authentication

The browser supplies an `Origin` header for these cross-origin POST requests, and browser JavaScript cannot set that protected header to an arbitrary value. The server checks the origin against the site's allowlist before it issues a bootstrap token or accepts events. CORS tells browsers which responses scripts may read; a successful preflight alone does not prove that a site is enabled for collection.

Browsers use CORS to decide whether JavaScript can read a cross-origin response; CORS is not authentication for all HTTP clients. In this implementation, a syntactically valid but unlisted origin can receive a 403 response with CORS headers, but it does not receive a bootstrap token or accepted events. A direct HTTP client is not a browser and can construct its own request headers, including an `Origin` value. A client that knows the public site ID and supplies an allowed origin can therefore request a bootstrap token if the site's other gates pass, then submit plausible events. The origin check should not be presented as proof of a human visitor, a trusted device, or an unforgeable source. This is a general boundary of browser-based analytics, not a reason to expose server secrets.

WhoVisited adds resource limits too: bootstrap requests are rate-limited per site, event ingestion uses a per-site rate limit, payloads are bounded, and accepted events count against the workspace's monthly quota. Those limits reduce uncontrolled consumption, but they do not turn a public browser tracker into fraud detection or an accounting system.

Keep the browser collection boundary narrow

  1. List only the HTTPS origins that should collect data. Add production subdomains explicitly; the configured HTTPS main domain covers its common `www` version, but not every arbitrary subdomain.
  2. When your site allowance permits it, use a separate staging site configuration to test without mixing events into production. Do not publish real customer data in test events.
  3. Do not put `INGESTION_SECRET`, `SESSION_SECRET`, API keys, bearer tokens, or database credentials into HTML, a Client Component, a `NEXT_PUBLIC_` variable, or an event property. The temporary ingestion token is also a bearer credential; do not paste it into an issue or share a full network export.
  4. Send only the fields needed for the report. Avoid names, email addresses, account identifiers, tokens, and form contents in event properties, page titles, or URL paths.
  5. If a site should stop collecting, disable its collection setting. Keep your own account, payment, and signup database as the authority for whether those actions happened.

You can inspect the snippet and request sequence in browser developer tools: a bootstrap request comes first, followed by event requests. The site ID may be visible; the short-lived bearer token should remain in the request and not be shared. A visible ID by itself is not evidence that someone accessed your workspace.

Test the boundary without treating analytics as a ledger

  1. On a test website, verify that a page from an unlisted origin does not collect through the browser, then add the intended origin and confirm the tracker can bootstrap there.
  2. Inspect the event request status and confirm that the synthetic visit appears under the intended site. Do not include or copy the Authorization header when documenting the test.
  3. Trigger a real test outcome from your application and check that its event matches the configured goal. Separately compare the result with your server-side record; an analytics event is a reportable browser signal, not proof of account creation or payment.
  4. Review monthly usage if event volume changes unexpectedly. The server applies rate and quota limits, but a limit being present does not establish that every accepted event is genuine.

For the installation steps, see the [website analytics installation guide](https://whovisited.net/guides/install-website-analytics). For browser request failures, use the [tracker debugging checklist](https://whovisited.net/blog/analytics-tracker-loads-but-no-events). MDN explains the browser role of the [`Origin` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin) and [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS); OWASP's [API4 resource-consumption guidance](https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/) describes why rate and payload limits matter.

References and further reading

  1. MDN: Origin header
  2. MDN: Cross-Origin Resource Sharing
  3. Next.js: Server and Client Components
  4. OWASP: API4 Unrestricted Resource Consumption