Webority.Analytics
0.6.0
dotnet add package Webority.Analytics --version 0.6.0
NuGet\Install-Package Webority.Analytics -Version 0.6.0
<PackageReference Include="Webority.Analytics" Version="0.6.0" />
<PackageVersion Include="Webority.Analytics" Version="0.6.0" />
<PackageReference Include="Webority.Analytics" />
paket add Webority.Analytics --version 0.6.0
#r "nuget: Webority.Analytics, 0.6.0"
#:package Webority.Analytics@0.6.0
#addin nuget:?package=Webority.Analytics&version=0.6.0
#tool nuget:?package=Webority.Analytics&version=0.6.0
webority-analytics
The fleet's product-analytics instrumentation layer (EPIC-00297, project 43). One concern, one repo, two published packages (hybrid npm + NuGet, mirroring webority-ui):
| Package | Registry | What |
|---|---|---|
@webority/analytics (analytics-js/) |
npmjs.org | Browser SDK. PostHog wrapped once: consent-gated init, PublicId-only identify, the locked event vocabulary as typed helpers, registerEvents guard (an unregistered name is dropped with a one-time warning, never sent) |
Webority.Analytics (Webority.Analytics/) |
nuget.org | Server SDK. Options-bound (Analytics section, IsConfigured degrade), channel-buffered background PostHog /batch/ sender via IHttpClientFactory, IFeedbackRelay to the OS feedback-ingest endpoint |
initAnalytics takes host, clientKey, product, surface, environment, and optionally
enabled, logger and sessionRecording. initConsent must run first, or initAnalytics throws:
PostHog captures only while the visitor's analytics consent is granted, and nothing else can switch
it. (Breaking in 0.5.0: the getConsent option and setConsent(bool) are gone. A portal adds
initConsent({ requiresOptIn, cookieDomain }) ahead of initAnalytics.) Session replay is OFF unless a product
passes sessionRecording: { enabled: true }; maskAllInputs (default true) and maskTextSelector
ride along only when it is on. Turning replay on is a disclosed product decision (it records page
content), so the package never defaults it.
Sending an event name that was never registered never throws. It is warned about once per name
and dropped, matching IAnalyticsClient.Track on the server half, which also logs and drops
rather than throwing. The warning goes to the logger passed to initAnalytics, preferring its
warn method and falling back to info; with no logger it goes to the browser console, so the
mistake is visible by default. The set of already-warned names is capped at 50.
registerEvents still throws on a name that is not snake_case: that is a startup declaration, not
a send in a user's click handler.
The server sender attempts a batch at most three times, pausing 1s and then 2s between attempts,
when the failure is one that passes on its own: a network error, a timeout, or status 408, 429 or 5xx. Every other 4xx and
anything thrown before a request exists is dropped at once, because the same request would fail the
same way. On a 429 or a 503, a Retry-After header replaces the fixed pause, in either the
delta-seconds or the HTTP-date form. It is never obeyed below the pause it replaces, so a zero
cannot start a tight loop, and never above 30 seconds, so a bad value cannot stall the sender. After the budget the batch is dropped and the loss is logged. Capture stays lossy by
contract: it never blocks the caller, and the queue drops new events rather than growing.
Analytics:Enabled is the server package's single on-off switch. False stops event capture and the
feedback relay together, so a product that turns analytics off sends nothing at all.
posthog-js is an optional peer dependency: the product installs it, and our package uses that
one copy. It is needed only by the main entry (initAnalytics and the rest of PostHog capture);
a /site-only consumer never loads PostHog, so it does not need to install it. A product that
also uses PostHog directly would otherwise end up with two engines running, each with its own
session and identity, which splits every funnel silently.
Consent, marketing tags and attribution (@webority/analytics/site)
Public websites import @webority/analytics/site, which carries no PostHog. Razor pages load the same code as
dist/webority-analytics-site.global.js, which exposes window.WeborityAnalytics. The main entry re-exports all
of it for portals. Call order is fixed, and getting it wrong throws at startup:
import { captureAttribution, initConsent, loadTags } from "@webority/analytics/site";
initConsent({ requiresOptIn, cookieDomain }); // first; from the server's country check, see below
// or, to ask every visitor first: initConsent({ strictOptIn: true, cookieDays: 182 });
loadTags({ gtmId, clarityId, ahrefsKey }); // ids from marketing.json#seo via apply-tokens
captureAttribution(); // every page load, before a router strips the query
initConsent({ requiresOptIn })sets Google Consent Mode's defaults: denied in the EEA, the UK and Switzerland, granted everywhere else. Then it applies the visitor's savedwebority_cookie_consentchoice.requiresOptIncomes from the host, which knows the country, for example from Cloudflare'sCF-IPCountryheader throughrequiresOptIn(countryCode). An unknown country opts in.cookieDomain(for example".capnix.ai") makes one choice cover the website and every portal subdomain. Leave it out and the choice stays on the current host. Adding it is safe for visitors who chose before it was set: when a host-only cookie sits beside the shared one, the newer choice wins,initConsentfolds the two into the shared cookie, and every save clears the host-only one. A newer choice or a withdrawal is never hidden by an older cookie.initConsent({ strictOptIn: true })is for a site that asks every visitor first, in every country. Consent Mode starts denied everywhere, with no region split, andloadTagsholds Tag Manager and Ahrefs until the visitor consents (see below).requiresOptInmay be left out; passingfalsethrows, because it contradicts the mode. Off by default, so existing sites keep the regional defaults.adStoragesays whether marketing consent also grants Google'sad_storage,ad_user_dataandad_personalization. It defaults totrue, and tofalseunderstrictOptIn, where Accept then grants analytics only. A strict site that runs advertising tags passesadStorage: true.cookieDayssets how long the saved choice lasts, counted from the choice. It defaults to 365; it must be a whole number above zero.getConsent()returns{ essential, analytics, functional, marketing, decided }.decided: falsemeans the banner should ask.saveConsent(preferences)writes the cookie (365 days unlesscookieDayssays otherwise), sends a Consent Mode update, and notifiesonConsentChangelisteners and awebority-consent-changewindow event. The analytics category is also what switches PostHog's capture on and off.loadTagsloads Tag Manager on the first interaction, or 1.5 seconds after load, and Ahrefs straight away. Clarity loads only with analytics consent: lazily on scroll or after 3 seconds, or when consent is granted later. A revoke takes effect from the next page. GA4 is not loaded here: it belongs inside the Tag Manager container, and loading it twice counts every page view twice. UnderstrictOptIn, Ahrefs waits for analytics consent. Tag Manager waits for analytics consent, or for marketing consent whenadStorageis on; marketing alone withoutadStorageloads nothing, because the container would then only carry GA4. Either consent counts whether given on this page or saved on an earlier visit, and once it arrives Tag Manager still waits for the next interaction or 1.5 seconds.captureAttribution()andgetAttribution()recordutm_*,gclid,gbraid,wbraid,fbclid,msclkid,li_fat_id, an outside referrer (or areferrerquery parameter carried between our own domains) and the landing path.- The first touch is kept, and the last touch is replaced by each later campaign or referral visit. A direct visit changes nothing.
- With marketing consent the record lives 90 days in localStorage under
webority_attribution; without it, only for the tab. It moves between the two stores when consent changes. - The JSON matches the server's
MarketingAttribution, so products post it as it is.
On the server, Webority.Analytics provides the matching types. The package stores nothing; products keep
the attribution on their own records.
MarketingAttribution(FirstTouch,LastTouch) ofMarketingTouchbinds the browser JSON with ASP.NET's default web settings.AcquisitionChannelResolver.Resolve(touch)returns anAcquisitionChannel: Google, Meta, LinkedIn, Microsoft, Other or Direct.utm_sourcewins, and separators are ignored ("Google Ads", "google_ads").- A click id decides when the source is missing or unrecognised.
- A tagged but unrecognised source is Other. No campaign parameter, or no touch at all, is Direct.
ConsentRegion.RequiresOptIn(countryCode)gives server-rendered pages the value to pass toinitConsent. It reads the country fromConsentRegion.CountryHeaderName(CF-IPCountry), and a test holds its list in step with the browser package's.
Products consume these; nothing product-specific lives here. Event names are the fleet vocabulary locked on EPIC-00297. Extending it is a fleet decision, not a per-product edit.
Config reaches products via .webority/marketing.json → apply-tokens/prebuild → appsettings
(~/.claude/conventions/marketing.md). The PUBLIC PostHog client key is committed product
config; the server-side personal API key never leaves OS config.
Publishing: on merge to main, per cicd.md package-repo standard (versions named by the
owner).
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Webority.Analytics:
| Package | Downloads |
|---|---|
|
Webority.Ui.Razor
Webority shared Razor component library — Tag Helpers + ViewComponents for the marketing subset, bundling the @webority/theme design system as a static web asset. |
|
|
Webority.Ads
Ad platform core for Webority products: the AdConversion outbox row and its queue and dispatcher, the AdLead intake log with its handler seam, normalise-then-hash of customer data, and the EF Core mappings a product applies to its own DbContext. |
GitHub repositories
This package is not used by any popular GitHub repositories.