Skip to main content
View as Markdown

Analytics

Sovrium ships a built-in, first-party analytics engine. It tracks page views, sessions, referrers, UTM campaigns, and device breakdowns on a single unified event model — with no cookies, no fingerprinting, and no external services. All data stays on your server, which makes it GDPR-friendly by default.

Analytics is configured via the analytics property in the app schema. When enabled, Sovrium injects a lightweight (~1KB) tracking script that records page views via navigator.sendBeacon() to POST /api/analytics/collect.

Configuration

analytics accepts a boolean shorthand or an object for fine-grained control.

app.yaml
# Boolean shorthand — enable with all defaults
analytics: true

# Object form — override individual settings, keep defaults for the rest
analytics:
  retentionDays: 365
  excludedPaths:
    - /admin/*
    - /api/*
  respectDoNotTrack: true
  sessionTimeout: 30
Property Default Range / Values Description
retentionDays 365 1730 Days to retain analytics data. Older rows are automatically purged.
excludedPaths [] string[] (glob) URL patterns excluded from tracking (e.g. /admin/*, /api/*).
respectDoNotTrack true boolean Honor the browser DNT:1 header — those visitors are not tracked.
sessionTimeout 30 1120 (minutes) Idle period after which a new session begins.

Unified Events Model

All analytics events — page views, custom track calls, and future sources — share a single system.analytics_events table with an event_type discriminator column. This follows the industry-standard pattern used by PostHog, Mixpanel, and Amplitude, but self-hosted with no external dependency by design.

code
system.analytics_events
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid()
  app_name        TEXT NOT NULL
  event_type      TEXT NOT NULL          -- 'page_view' | 'track' | extensible
  event_name      TEXT NOT NULL          -- path for page_view, custom name for track
  org_id          TEXT                   -- organization scope (nullable pre-auth)
  timestamp       TIMESTAMPTZ NOT NULL DEFAULT now()
  visitor_hash    TEXT NOT NULL          -- privacy-preserving visitor identifier
  session_hash    TEXT NOT NULL          -- privacy-preserving session identifier
  properties      JSONB NOT NULL DEFAULT '{}'
Event type event_name properties payload
page_view The page path path, referrer, utm_*, device_type, browser_*, os_*, screen_*, language, country.
track The custom event name Arbitrary key-value metadata from an automation action or the tracking API.
link_click The link slug Which short link was followed, the target it resolved to, plus the same referrer / device / UTM keys as a page view.
qr_scan The link slug A short link reached by scanning its QR code, counted apart from the same link pasted into a message.

The promoted columns (event_type, event_name, org_id, visitor_hash, session_hash, timestamp) drive efficient WHERE/GROUP BY queries; everything else lives in JSONB. The same privacy primitives — server-side visitor/session hashing (SHA-256, no PII stored), DNT respect, and the retention horizon — apply uniformly to every event type, and every row is org-scoped for multi-tenant isolation.

Querying Analytics

Aggregated analytics are exposed through admin-gated read endpoints. Inspect raw events for debugging or custom dashboards.

Endpoint Returns
POST /api/analytics/collect Public ingest endpoint hit by the tracking script.
GET /api/analytics/overview Totals + bucketed daily time series.
GET /api/analytics/pages Top pages by view count.
GET /api/analytics/referrers Referrer and UTM-campaign breakdown.
GET /api/analytics/devices Device-type and browser breakdown.
GET /api/analytics/campaigns UTM campaign analysis.
GET /api/analytics/events Admin-only raw event inspection (paginated, filterable).

Every aggregated endpoint above accepts an optional event_type and event_name, defaulting to event_type=page_view. Passing ?event_type=link_click&event_name=spring-promo reports one short link through the very same time series, referrer, device and campaign breakdowns that serve page views. That is one set of reports over one store, rather than a parallel set that could disagree with it.

Inspecting raw events

GET /api/analytics/events provides row-level visibility into the unified stream — complementing the aggregated endpoints. It is admin-only and supports filtering by event_type, event_name, and date range.

code
GET /api/analytics/events?event_type=track&limit=50&offset=0&from=2026-01-01&to=2026-04-15
Parameter Description
event_type Filter by type (page_view, track, …).
event_name Exact-match event name.
from / to ISO 8601 date range (inclusive).
limit Max results (default 50, max 1000).
offset Pagination offset (default 0).

Privacy & Compliance

  • No cookies, no fingerprinting. Visitors are identified by a server-computed SHA-256 hash; no client-side identifier is stored.
  • DNT respected by default. Visitors sending DNT:1 are not tracked when respectDoNotTrack is on.
  • All data is local. No third-party services and no external calls — analytics never leaves your server.
  • Bounded retention. Data older than retentionDays is automatically purged, aligning with the platform's data-minimisation posture (see Ecoconception).

Last updated August 28, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium