# How we handle visitor data

Here is what happens on one page view.

Tinylytics counts visits without cookies, without fingerprinting, and without storing a visitor IP on the hit. We keep what you need to see traffic. We do not keep a person.

## The script loads

The embed is a small JavaScript file from `tinylytics.app`. Cloudflare sits in front. A visitor to your site does not get a Tinylytics cookie. Whether you still show a banner is your call.

Once the script runs, it sends a request with:

- the page URL and path
- the referrer, if the browser sent one
- the user agent

The browser also sends an IP to Cloudflare. We see Cloudflare’s connecting IP, not a raw client IP that skipped the edge. That address is used for a hash and a country lookup. It is not written onto the hit.

## Before anything is stored

Bots, prerenders, and blank user agents are dropped. A source that sends more than 25 hits a minute is rate-limited. Obvious spam is ignored. Bursts from the same hash are suppressed so a stuck tab or a scraper does not become your traffic.

If you visited your own site with `?tiny_ignore=true`, that browser has a localStorage flag and we never record the hit. See [ignoring your hits](/docs/analytics/ignore-hits).

## Unique hits, without a person

Total hits count every load. Unique hits try not to count the same browser twice in a day. We do not store a visitor ID to do that.

We build a one-way SHA-256 hash from:

1. the Cloudflare connecting IP
2. your site’s id
3. the first 50 characters of the user agent
4. today’s date (UTC)
5. a salt that rotates every 12 hours

That is the **visitor hash**. Same browser, several pages, one visitor for the day. Sessions and journeys use this one.

The **unique hash** is the same mix plus the path. That is the per-page unique on a path row.

The hash is not reversible. Tomorrow the date changes, so it is a new hash. The salt rotates twice a day, so even same-day brute force is not a useful hobby. Two browsers are two hashes. A shared office NAT can collapse several people into one. That is the trade: approximate unique hits, no durable person.

## What a hit looks like

After the hashes are built, we store something in this shape:

- site
- path and URL
- referrer and UTM-style source, when present
- country (two-letter code)
- truncated user agent
- the two hashes
- timestamps

No IP. No name. No cookie. No cross-site id. Country came from the IP, then the IP was discarded.

User agents on hits older than four weeks are cleared. Server logs are filtered and deleted after 7 days. They do not keep client IPs unless someone is trying to go around Cloudflare.

## Country

Country is country. Not city, not lat/long. The lookup happens, the code is stored, the IP is gone. If you send hits through the API with an `ip_address`, we try a local lookup first. Only unresolved lookups go to IPinfo, then that IP is discarded too.

## What you see in the dashboard

Overview, paths, referrers, countries, browsers, platforms. [Unique hits](/docs/analytics/unique-hits) if you turned them on. Sessions and journeys are the visitor hash for that day, not a returning customer next week.

Optional [Insights](/docs/analytics/insights) send aggregated counts (traffic, paths, referrers) to an AI provider. Opt-in. Not used for training. No IP, no hashes, no user agents in that payload.

## Your account

Signed-in people get one session cookie. That is for you, not for your visitors. Export or delete the account whenever you want. Unconfirmed accounts are removed after 7 days.

The app runs in Falkenstein, Germany, on Hetzner. See [hosted in Europe](/docs/trust/hosted-in-europe).

Payments go through Stripe Link. Some accounts still use Paddle or Lemon Squeezy. Errors go to Sentry with a customer id, not a visitor. Uptime checks send the site URL to Thunder Clap, not a person.

The [privacy policy](/docs/privacy) is here if you need it. Questions: hello@tinylytics.app.