# How Supapulse works

URL: https://supapulse.app/docs/data-privacy/how-it-works

The path of a pageview from the browser to your dashboard, how visitors are counted without cookies, and how salt rotation works.

## The data flow [#the-data-flow]

<Steps>
<Step>

**Browser.** The tracker sends `POST /api/ingest` with the public token, the page URL, referrer, title, browser language, UTM parameters and the in-memory session ID. It reads and writes no cookies or storage.

</Step>
<Step>

**API.** Checks the token and that the request's `Origin` matches the site's domain, queues the event with the request's IP address and user agent, and answers `202`.

</Step>
<Step>

**Worker.** Builds the visitor hash, looks up the country from the IP address, reads browser, OS and device type from the user agent, and stores the pageview. The record has no IP address and no user agent.

</Step>
<Step>

**Dashboard.** Counts visitors by hash, sessions by session ID, and joins revenue to pageviews by visitor hash, session ID or customer.

</Step>
</Steps>

## Visitor identification [#visitor-identification]

```text
visitor_hash = SHA-256(ip_address + user_agent + site_id + site_salt)
```

| Input | Why |
| --- | --- |
| IP address | Tells visitors apart. Used for the hash and the country lookup, never stored on the pageview. |
| User agent | Tells apart people on the same network. |
| Site ID | The same person gets a different hash on every site, so visits can't be linked across sites. |
| Site salt | A random 64-character secret per site. Replacing it breaks the link to every earlier hash. |

The hash is computed on the server. The browser only gets it if your code calls `getVisitorFingerprint()`, which asks the server for it so you can pass it to checkout. Nothing else in the tracker reads it.

What this means in practice:

- Two people behind one IP address with identical browsers count as one visitor.
- One person who switches networks (Wi-Fi to mobile data) counts as two.
- After a salt rotation, everyone counts as new.

## Salt rotation [#salt-rotation]

Each site's salt is replaced on a schedule. An hourly job rotates every salt older than the site's rotation period.

| Setting | Rotation period |
| --- | --- |
| Default | 24 hours |
| Options | 24 hours, 3 days, 7 days, 14 days, 30 days |

Change it in the site's settings. Changing the period rotates the salt immediately, and you can also rotate it by hand at any time.

A longer period means returning visitors are recognized for longer, so "unique visitors" over a week is more accurate with 7 days than with 24 hours. A shorter period means a hash can be linked to fewer visits. Pick the shortest period that answers your questions.

## Sessions [#sessions]

The session ID is a random UUID created by the tracker for each page load and kept in memory. Client-side navigations keep it; a reload or a new tab starts a new one. See [Sessions](https://supapulse.app/docs/core/sessions).

## Revenue [#revenue]

Revenue comes from the Stripe webhook or from your server with the secret token. Supapulse credits it to the first and last touch of the visit it can match: by visitor hash, then session ID, then the customer ID you identified. The customer ID is stored as a one-way hash. See [Attribution](https://supapulse.app/docs/revenue/attribution).

## Why server-side [#why-server-side]

Browser fingerprinting reads canvas, fonts and hardware details to build a stable ID that follows a person around. Supapulse doesn't do that. Its hash uses only what every HTTP request already carries, changes on every salt rotation, and is different on every site.
