Analytics & Tracking Integrations
At a glance
Analytics & Tracking Integrations gives every tenant a privacy-first analytics stack — Plausible (cloud or self-hosted) and Umami as cookie-free defaults, optional Google Analytics 4 gated strictly on cookie consent, PostHog for product analytics, feature flags, and funnel analysis, and a per-tenant CookieConsentConfig that drives the consent banner across four standard categories, integrates with the existing CookieConsentRecord, and honours Do Not Track signals out of the box.
How it works
PlausibleConfig holds Key Vault credential refs (api_key_ref), site_id, instance_url (cloud or self-hosted), enable_custom_events, enable_revenue_tracking, allowed_domains, and AnalyticsUsageMetrics; AnalyticsEvent is a shared collection across providers with config_id, provider, event_name, event_properties, user_id, anonymous_id, page_url, referrer, user_agent, and idempotency_key. POST /analytics/plausible/events/ ships events via the Plausible Events API — fully GDPR-compliant and cookie-free. UmamiConfig is the self-hosted alternative with credentials (api_key_ref, username_ref, password_ref), instance_url, website_id, share_url, enable_custom_events, and track_outbound_links; events flow through POST /analytics/umami/events/ to the Umami Collect API, also cookie-free.
GA4Config carries measurement_id, api_secret_ref, optional service_account_json_ref for server-side calls, property_id, data_stream_id, require_cookie_consent (default true), anonymize_ip, enable_enhanced_measurement, enable_ecommerce, debug_mode, and usage; POST /analytics/ga4/events/ uses the GA4 Measurement Protocol with client_id, but the integration refuses to fire until a CookieConsentRecord with the analytics category exists for the visitor. CookieConsentConfig is the per-tenant control panel — a ConsentBannerConfig (position, style, primary/background/text colors, show_reject_all, show_preferences), four ConsentCategoryConfig blocks (necessary, functional, analytics, marketing) with enabled/required/default_accepted/display_name/description/cookie_names, privacy_policy_url, cookie_policy_url, consent_expiry_days (1–730), geo_targeting_enabled, and respect_do_not_track. The config integrates with the existing CookieConsentRecord (privacy.py) so individual visitor decisions are persisted, scoped per category, and revisited on expiry.
PostHog rounds out the stack with event tracking for product analytics, feature flags, and funnel analysis under per-tenant configuration so federations can experiment safely.
Key capabilities
- Plausible (cloud or self-hosted) cookie-free analytics with custom events
- Umami self-hosted alternative with Collect API and outbound-link tracking
- Google Analytics 4 via Measurement Protocol, gated on cookie consent
- PostHog for product analytics, feature flags, and funnels
- CookieConsentConfig with banner styling, four standard categories, and expiry control
- CookieConsentRecord integration for per-visitor decisions and revocation
- respect_do_not_track and geo_targeting flags for jurisdictional compliance
In practice
A federation launches a new CMS site. The webmaster activates a PlausibleConfig pointing at their self-hosted Plausible instance, no consent banner needed for analytics traffic. They also enable PostHog for funnel analysis on the registration flow, since both providers run cookie-free.
A regional sponsor later asks for GA4 reach reporting, so the webmaster creates a GA4Config and publishes a CookieConsentConfig with analytics defaulting off, banner copy in Swedish, expiry 365 days, and respect_do_not_track=true. Visitors now see the banner; those who accept analytics generate GA4 events, those who reject still feed Plausible cookie-free totals, and the consent store records every decision with the right category granularity.
Features in this subsystem
24| ID | Status | Features |
|---|---|---|
| F14.16.01 | Shipped | Plausible analytics (privacy-respecting) — PlausibleConfig-samling tenant-scopad med display_name/credentials (PlausibleCredentialRefs api_key_ref)/site_id/instance_url (cloud eller self-hosted)/enable_custom_events/enable_revenue_tracking/allowed_domains/usage (AnalyticsUsageMetrics); CRUD /analytics/plausible/configs/ med livscykel (draft→active→paused→error→decommissioned); AnalyticsEvent-samling med config_id/provider/event_name/event_properties/user_id/anonymous_id/page_url/referrer/user_agent/idempotency_key; POST /analytics/plausible/events/ skickar via Plausible Events API; GDPR-kompatibel utan cookies ✅ PL-F1416 |
| F14.16.02 | Shipped | Umami analytics som självhostat alternativ — UmamiConfig-samling tenant-scopad med display_name/credentials (UmamiCredentialRefs api_key_ref/username_ref/password_ref)/instance_url/website_id/share_url/enable_custom_events/track_outbound_links/usage; CRUD /analytics/umami/configs/ med livscykel; POST /analytics/umami/events/ skickar via Umami Collect API; privacy-fokuserad, cookiefri ✅ PL-F1416 |
| F14.16.03 | Shipped | Google Analytics 4 (opt-in per tenant) — GA4Config-samling tenant-scopad med display_name/credentials (GA4CredentialRefs measurement_id/api_secret_ref/service_account_json_ref)/property_id/data_stream_id/require_cookie_consent (default true)/anonymize_ip/enable_enhanced_measurement/enable_ecommerce/debug_mode/usage; CRUD /analytics/ga4/configs/ med livscykel; POST /analytics/ga4/events/ skickar via GA4 Measurement Protocol med client_id; kräver cookie-samtycke (analytics-kategori) innan spårning aktiveras ✅ PL-F1416 |
| F14.16.04 | Shipped | Per-tenant cookie consent — CookieConsentConfig-samling tenant-scopad med display_name/status/banner (ConsentBannerConfig position/style/primary_color/background_color/text_color/show_reject_all/show_preferences)/categories (necessary/functional/analytics/marketing med ConsentCategoryConfig enabled/required/default_accepted/display_name/description/cookie_names)/privacy_policy_url/cookie_policy_url/consent_expiry_days (1–730)/geo_targeting_enabled/respect_do_not_track; CRUD /analytics/cookie-consent/configs/ med PATCH-uppdatering; integrerar med befintlig CookieConsentRecord (privacy.py) för individuella samtyckesbeslut ✅ PL-F1416 |
| F14.16.05 | Shipped | PostHog event tracking integration for product analytics, feature flags, and funnel analysis with per-tenant configuration ✅ PL-F1416 |
| F14.16.06 | Shipped | API usage analytics aggregation per developer/tier — RateLimitWindow flushes till ApiUsageRecord-buckets; admin /admin/api/usage-vy med stacked area chart per tier, drilldown per utvecklare ✅ PL-T215 |
| F14.16.07 | Shipped | Per-record provenance metadata — _meta.dataset_version + _meta.provenance_uri + field_provenance på alla data-product-svar för spårning av källdataset och version ✅ PL-T215 |
| F14.01.10 | Shipped | Developer portal self-service — POST /developer/keys, GET/DELETE /developer/keys/{id}, GET /developer/keys/{id}/usage med 256-bit-key + SHA-256-hash; X-Developer-Id-bootstrap-header för första key-issuance ✅ delvis a95566cc.m-1 (nyckel-CRUD, se F14.00.05 — klienten ensureas ur SESSIONEN, X-Developer-Id-bootstrapen finns INTE och kommer inte); …/usage ⊘ a95566cc.m-2, portalens UI ✅ a95566cc.m-7 t-3 (web /developers/nycklar) |
| F14.01.11 | Shipped | Sandbox-tenant provisioning — POST /developer/sandbox med 4 seed-templates (minimal, national_federation, club, competition_in_progress), separat PostgreSQL-databas, 30-dagars auto-expiry, 10× rate-limit ✅ a95566cc.m-7 (POST|GET /v1/developer/sandbox, …/templates, …/{id}:extend, …:reset, DELETE …): en RIKTIG tenant (tenant_type='sandbox', server-myntad kod sbx-<16 hex>, land XS) född genom husets ENDA defaults-väg, med de fyra mallarna byggda av de ägande domänernas EGNA sömmar och ett serverat manifest med ärliga omits[]; 30 dagars frist (block om 30, tak 120, varning 7) genom svepet developer_sandbox_expiry_tick; m-1:s ×10-budget genom is_sandbox + X-Petanque-Environment: sandbox. TRE legacy-löften ERSATTA (spec §9b): »separat PostgreSQL-databas« ⇒ tenant-isolation (arkitekturen har EN Postgres) · »raderas 1 dygn efter expiry« ⇒ stängning (klient återkallad + tenant arkiverad; huset har ingen tenant-hard-delete) · 429 vid kvot ⇒ 409 sandbox_quota_exceeded med {tier, limit, used}. Kontraktet: specs/api/endpoints/developer-sandbox.md §Greenfield. Portalens YTA (web /developers/sandlada) är BYGGD i a95566cc.m-7 t-3: mallarna med sina SERVERADE manifest och luckornas ägare cell för cell, beståndet med frist/dagräknare, :extend/:reset/DELETE och serverns 409 sandbox_quota_exceeded renderat VID åtgärden (aldrig en förhandsutgråad knapp). Operatörens motsvarighet är sys-panelen *Sandbox-tenanter* (/utvecklarplattform?flik=utvecklare) — en ren LÄSNING utan en enda mutationsknapp, där mockupens databaskolumn är ersatt av Tenant (§9b D-3) och *Provision sandbox (operator override)* står som ⊘ med skäl och pekar på den väg som FAKTISKT finns (impersonation med samtycke, D-SYS m-5) — §9b D-6 |
| F14.01.12 | Shipped | OAuth 2.0 client-credentials grant — POST /v1/oauth/token med scope-baserad authorization (api:read, webhooks:write, data:integrity etc.); 3600s opaque token-livslängd; key-id-blacklist vid revoke ✅ a95566cc.m-1 — granten och key-id-blacklisten; tokenen är kortlivad och signerad (bokförd avvikelse mot legacys 3600 s opaka token) |
| F14.01.13 | Planned | Public OpenAPI surface — GET /v1/openapi.json filtrerat till canonical tags (CANONICAL_DOMAINS frozenset i domains.py) ⊘ a95566cc.m-2 (GET /public/openapi.json, den filtrerade projektionen) |
| F14.01.14 | Planned | API-versionering policy + middleware — /v{N} URL-prefix, X-API-Version/X-API-Version-Deprecated/Sunset headers, route-aware aliasing (explicit /v1/X-routes passerar genom oförändrade) ⊘ a95566cc.m-2 (versionslivscykeln) |
| F14.01.15 | Shipped | Sliding-window rate-limit per client + tier — Redis sorted-set per (client_id, bucket); per-endpoint-buckets (default, webhook_management, data_product_query, oauth_token); fail-open vid Redis-utfall ✅ a95566cc.m-1 — per klient/tier, in-memory sliding window per instans (Redis är avvecklad, arkitektur §3) |
| F14.01.16 | Planned | Legacy federation field-mapping — nationsprofilkatalogen GET /v1/federation/import-nation-profiles (SBF · NBoF · FFPJP ifyllda ur legacy-federation-mapping.md; FBP · FIB · FEP available: false med skäl och TOM mappning) + förbundets EGNA recept (/v1/federation/import-formats, mappning per kolumn med sluten transformmängd) och fältmappnings-editorn i admin /dataexport. Legacy-radens tenant.field_mapping_overrides ERSATT: mappningen är en TENANT-ÄGD rad, inte en override i tenant-konfigurationen — profilen KOPIERAS vid sparandet och läses aldrig igen. FIPJP-/CEP-formatet ligger kvar hos truppinlämningen (F14.02.03/.04) byggt av D-PUBAPI m-9 (a95566cc-serien, federations-/legacy-importen) |
| F14.03.07 | Shipped | Signed payload v2 — kanonisk "{timestamp}.{råa body-bytes}" HMAC-SHA256 (Stripe-formen), 5 min replay-fönster, headerfamiljen X-Petanque-Signature/-Timestamp/-Event-Id/-Delivery/-Attempt (+ -Replay-Of, -Environment) genom husets ENDA header-writer byggt av a95566cc.m-3 |
| F14.03.08 | Shipped | Self-service CRUD + debugging — /v1/developer/webhooks*, leveransloggen med per-försök-status, :test, :replay med X-Petanque-Replay-Of byggt av a95566cc.m-3 |
| F14.03.09 | Shipped | DLQ + auto-disable — 8 försök (backoff bas 30 s, kapad 1 h) ⇒ dlq; prenumerationen auto-avstängs efter 24 h med 100 % fel (disabled_reason='too_many_failures') av webhook_health_sweep, med audit (systemaktör) + in_app-notis. Ingen självläkning — :enable är en människas handling byggt av a95566cc.m-3 |
| F14.03.10 | Shipped | Sandbox-säker routning — en sandbox-klient når bara PL_WEBHOOK_SANDBOX_TARGET_ALLOWLIST (default webhook.site + loopback i local/test); prövat vid BÅDE registrering (403 webhook_target_not_allowed med allowlisten i detail) och sändning; leveransen bär X-Petanque-Environment: sandbox byggt av a95566cc.m-3 |
| F14.03.11 | Planned | ed25519-overlay — ed25519:<kid>=<hex> som tillägg till HMAC på integritetstyper (sanction.created); publik nyckel via GET /v1/integrity/public-key (ETag/304, 404 fail-closed) och roterade föregångare i previous[]. ⊘ Kvartalsvis AUTOMATISK rotation är INTE byggd — rotationen är en manuell driftåtgärd, runbooken §2. Ägare: D-PUBAPI (senare milstolpe) byggt av a95566cc.m-3 (rotationsautomatiken ⊘) |
| F14.04.10 | Shipped | Insurance risk-feed (aggregerad) — GET /v1/insurance/snapshots/{period} med månads-/kvartalssnapshots; competitions/venues/incidents-aggregat per region (NUTS-2), surface, capacity-band; väderdata per outdoor-event-hours; inga PII ✅ PL-T215 |
| F14.05.07 | Shipped | GraphQL analytics-endpoint — POST /v1/analytics/graphql query-only med complexity-limit (count("{")*10 + count("(")*2, default 1000); per-tier-tak; field-level scope-gating ✅ PL-T215 |
| F14.05.08 | Shipped | Research cohort query — POST /v1/research/cohorts/{id}/query med pseudonymized k=5-anonymity, consent-version per record, ethics-committee-approval-flow ✅ PL-T215 |
| F14.05.09 | Shipped | Integrity event stream (SSE) — GET /v1/integrity/events/{event_id}/stream med Last-Event-ID-reconnect, p50<250ms latency-SLA, 15s heartbeat ✅ PL-T215 |
| F14.05.10 | Shipped | Sanctions-list för betting-operatörer — GET /v1/integrity/sanctions med since/jurisdiction-filter, player_pseudo_id, ed25519-signerat _meta ✅ PL-T215 |