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.
# 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 |
1–730 |
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 |
1–120 (minutes) |
Idle period after which a new session begins. |
Boolean vs object. analytics: true enables defaults (365-day retention, DNT respected, 30-minute sessions). Use the object form to override specific settings while keeping defaults for the rest. analytics: false (or omitting the property) disables analytics — no endpoints are mounted.
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.
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.
Storage-engine parity. The unified events table is exercised by both the PostgreSQL (->> over system.analytics_events) and SQLite (json_extract over system_analytics_events) query paths. Both produce identical response shapes for collect, overview time-series, top-pages, referrer/UTM, and device breakdown.
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.
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:1are not tracked whenrespectDoNotTrackis on. - All data is local. No third-party services and no external calls — analytics never leaves your server.
- Bounded retention. Data older than
retentionDaysis automatically purged, aligning with the platform's data-minimisation posture (see Ecoconception).
Related Pages
- Admin Dashboard — operator read API across the platform.
- Activity Monitoring — audit trail of user and admin actions.
- Automations — emit custom
trackevents from automation actions. - Ecoconception — data retention and environmental footprint.
- Database Infrastructure — the SQLite/PostgreSQL parity behind the events table.
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.