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.

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 if you turned them on. Sessions and journeys are the visitor hash for that day, not a returning customer next week.

Optional 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.

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 is here if you need it. Questions: [email protected].

© 2026 Tinylytics
Terms Privacy Contact