# Data kogut collects

Stored analytics, visitor ids, retention, and outbound connections.

kogut stores web analytics in SQLite on your server. Use this page for your privacy
notice; it is not legal advice.

## Browser data

The script does not access cookies, `localStorage`, `sessionStorage`, IndexedDB, or Cache
Storage. It does not inspect canvas, fonts, plugins, or hardware for fingerprinting.

| Event | Sent when | Fields |
|---|---|---|
| Page view | Page load, path change, or back/forward cache restore | Page URL, referrer on initial load, window width, and automation flag |
| Web Vitals | Page hide, exit, or route change | Available LCP, INP, CLS, FCP, and TTFB values |
| Error | Uncaught exception or unhandled rejection | Redacted message, source, line, column, stack, and fingerprint |
| Custom event | `kogut.track()` call or tagged click | Name and properties |
| Outbound link | Click to another host | `outbound` event with the destination URL |

Events include the page URL, the site domain, and the app version when the tag sets
[`data-version`](https://docs.kogut.dev/web-apps/tracking-script/#the-app-version); the version describes the build of
your app, not the visitor. Snippets with `data-website-id` omit the domain, and the
server finds the site from the page's host. Prerendered pages send nothing until
activation.

Page URLs lose credentials, fragments, and query parameters except `utm_source`,
`utm_medium`, `utm_campaign`, `utm_term`, and `utm_content`. Referrers and outbound links
lose queries and fragments. Requests use `fetch` with `keepalive`, without cookies or a
`Referer` header. Browsers also send request headers such as the user agent and language.

## The visitor id

The server computes a separate visitor id for each site: the first 16 bytes of the
SHA-256 hash of the salt, the site id, the length of the IP address, the IP address, and
the user agent, in that order.

The salt has 32 random bytes and changes daily at 04:00 in the server's time zone; the
previous salt is deleted. The current salt is stored in SQLite so ids survive restarts.
[Backups](https://docs.kogut.dev/high-availability/backups/) exclude it.

The same visitor gets unrelated ids on different sites and days. Visitor totals over
several days add the daily counts. A visit ends after 30 idle minutes or at the salt
change.

Raw IP addresses and user agents are not stored. The server uses the IP address for rate
limits and visitor ids, and the user agent for ids, bot filtering, browser, operating
system, and device class.

## Stored data

| Table | Fields |
|---|---|
| `hit` | Site, time, path, referrer host, channel, source, medium, campaign, country, device, browser, operating system, language, app version, visitor id, visit id, and duration |
| `visit` | Id, site, start, last seen, and page view count |
| `vital` | Site, time, path, device, app version, and available metric values, without a visitor id |
| `custom_event` | Site, time, name, properties, and visitor id |
| `error_group` | Id, site, fingerprint, message, source, first seen, last seen, count, and state |
| `error_event` | Group, time, path, browser, app version, and stack |
| `hit_bucket` | Daily totals and counts by dimension, without individual visitor ids |
| `vital_bucket` | Hourly histograms by site, path, and metric |
| `salt` | Current salt day and salt |

Daily rollups cover 04:00 to 04:00 in the server's time zone. Dimensions include paths,
referrers, channels, countries, devices, browsers, operating systems, languages, sources,
mediums, campaigns, and app versions. Each keeps up to 1,000 values per day, so rare values can
disappear from older breakdowns.

The country comes from Cloudflare's `CF-IPCountry` header; without Cloudflare in front of
kogut, no country is recorded. kogut makes no GeoIP lookups and stores no region or city.
Detected bots and automated browsers are dropped before storage.

Errors are [redacted](https://docs.kogut.dev/web-apps/javascript-errors/#reported-errors) in the browser and on the
server. Custom event values are redacted on the server, which does not remove all
personal data. Event names and property keys are not redacted.

## Kept only in memory

These records disappear on restart:

- Up to 50,000 open visits, with visitor id, visit id, and last event time.
- Rate counters for up to 10,000 clients, keyed by a hash of the IP and a startup secret.
- The last 200 page views' hashed client addresses and visitor ids, for proxy warnings.
- Up to 20 app versions per site, with the time each was last seen.

## Retention

Raw analytics, errors, daily rollups, and hourly histograms have separate settings in
[`[retention]`](https://docs.kogut.dev/reference/configuration/#retention). Error groups remain after their
events expire. The salt lasts until the next salt day.

## Outbound connections

Browsers load the script from and post events to your kogut only. The server connects to
check targets, DNS resolvers, domain registries and IANA's RDAP list, notification
services, configured OIDC providers, kogut edge, and a configured
[heartbeat URL](https://docs.kogut.dev/high-availability/heartbeat-url/). With nothing configured, it opens
no outbound connection, not even a DNS lookup.

While a check of a public address exists, the server also
[probes connectivity](https://docs.kogut.dev/alerts/how-alerts-work/#when-kogut-loses-its-internet-connection)
to the hosts in [`connectivity_hosts`](https://docs.kogut.dev/reference/configuration/#server). Agents
connect to your server and their assigned check targets.

kogut sends no telemetry and makes no update checks.

## MCP and Copy for AI

[MCP](https://docs.kogut.dev/reference/mcp/) requires an `mcp` token. Its tools return totals, breakdowns,
Web Vitals, and error groups, not visitor ids, individual page views, or custom event
properties.

[Copy for AI](https://docs.kogut.dev/incidents/copy-for-ai/) copies the incident timeline, likely causes,
alerts, and log excerpts. Likely causes can include JavaScript error counts.

## Consent

Whether you need consent depends on your site and visitors; using no browser storage does
not decide it. You can [load the script after consent](https://docs.kogut.dev/web-apps/tracking-script/#consent).

Do not send personal data in [custom events](https://docs.kogut.dev/web-apps/events-and-goals/#properties).
