Skip to content

Consent and GDPR

Every record Belemetry produces belongs to exactly one of twelve features, and a feature is collected only while it is both enabled by configuration and granted by the user. Nothing is sent before the user decides, each feature can be granted and revoked on its own at runtime, and revoking one purges what it queued and what it stored on the device.

Your consent, live

This panel is wired straight to IBelemetryConsent - the same service the site's banner uses. Tick a feature to grant it, untick it to revoke it, or set a whole group at once. The decision is remembered in this browser, and the Live telemetry page shows what each grant lets through.

Liveper-feature consent

Per feature

Groups

.NETIBelemetryConsent
Available
All (4095)
Granted
None (0)
IsDecided
not decided

Effective (configured and granted)

ErrorsPageViewsInteractionsPerformanceDependenciesSessionsUserTrackingLogsTracesMetricsPersistenceEvents
BrowserBitBelemetry.getContext()

Shown once the page is interactive.

Features

The bit values are a wire contract shared by C# and the browser script: they cross the interop boundary as plain integers and are persisted in the browser, so they never change.

FeatureBitCovers
Errors 1 JS errors and unhandled rejections, .NET logs at Error/Critical or with an exception, TrackException
PageViews 2 Page views - navigation and TrackPageView
Interactions 4 Clicks and other interactions
Performance 8 Web vitals, document load, resource timing, long tasks
Dependencies 16 Browser fetch and XHR spans
Sessions 32 A session id that survives reloads (sessionStorage)
UserTracking 64 enduser.pseudo.id (localStorage) and the authenticated user.id
Logs 128 .NET logs below Error, browser console capture
Traces 256 .NET Activity spans
Metrics 512 .NET Meter metrics
Persistence 1024 Unsent telemetry kept on the device for a later retry
Events 2048 Custom events (TrackEvent)

Groups

Banners rarely ask about twelve things. The groups are the usual three questions, as flags you can pass to SetAsync or combine with others:

GroupFeaturesAnswers
DiagnosticsErrors, Logs, Traces, DependenciesWhat is needed to troubleshoot the app
AnalyticsPageViews, Interactions, Sessions, UserTracking, EventsHow the app is used
MonitoringPerformance, MetricsHow the app performs
AllEvery feature (4095)For apps that need no consent

Persistence is in no group: keeping unsent telemetry on the device is a question of its own. Grant it with GrantAsync(BelemetryFeatures.Persistence), or include it in what you pass to SetAsync.

How consent behaves

  • Effective = configured AND granted. BelemetryOptions.Features (all by default) is what the app collects at all; IBelemetryConsent.Available reports it. A feature switched off by configuration is never collected, whatever is granted.
  • Opt-in by default. Before the user decides, only Consent.Default is granted - None unless you set it, for example to Errors when error reporting is covered by legitimate interest, or to All for an app that needs no consent.
  • Nothing undecided leaves the device. Records of features not yet granted are held in memory, up to Consent.PendingLimit (500). When the user decides, what they granted is sent and the rest is dropped.
  • Revoking is immediate. Queued records of a revoked feature are discarded, and its device storage cleared: revoking Persistence clears the offline queue, Sessions the stored session, UserTracking the pseudo id and the user id.
  • The choice is remembered (Consent.Persist, on by default) in localStorage under bit-belemetry-consent, and in a file in Hybrid apps. Recording the choice itself is strictly necessary, so it needs no consent of its own.
  • Global Privacy Control is honored (Consent.HonorGlobalPrivacyControl, on by default): a browser sending GPC never gets the Analytics features, whatever is requested.
  • No cookies. Belemetry sets none.
  • Trace context still flows. Without Traces, activities are still created for propagation, so downstream services keep receiving the trace context - but nothing is recorded.

On WebAssembly and Hybrid consent is app-wide. On Blazor Server it belongs to the circuit's user - it lives in their browser - so IBelemetryConsent needs an interactive render mode there. The server pipeline is governed by Server.Features instead, since it serves every user at once.

builder.Services.AddBitBelemetryServices(options =>
{
    options.Features = BelemetryFeatures.All;              // what the app collects at all (default)

    options.Consent.Default = BelemetryFeatures.None;      // granted before a decision (default)
    options.Consent.Persist = true;                        // remember the choice (default)
    options.Consent.HonorGlobalPrivacyControl = true;      // GPC: never Analytics (default)
    options.Consent.PendingLimit = 500;                    // held until the decision (default)
});

A banner of your own

A banner needs three things from IBelemetryConsent: whether to show itself (IsDecided), a way to record the answer (SetAsync), and Changed to hide itself when the answer comes from elsewhere - a settings page, or another tab. It should wait for the first interactive render and RefreshAsync: the decision lives in the browser, and a prerendered banner would flash at every user who already chose.

@implements IDisposable
@inject IBelemetryConsent Consent

@if (_ready && Consent.IsDecided is false)
{
    <div class="consent-banner" role="dialog" aria-label="Telemetry consent">
        <p>We measure errors and usage to improve the app. Nothing is sent until you choose.</p>
        <button @onclick="() => Consent.SetAsync(BelemetryFeatures.All)">Accept all</button>
        <button @onclick="() => Consent.SetAsync(BelemetryFeatures.Diagnostics)">Diagnostics only</button>
        <button @onclick="() => Consent.SetAsync(BelemetryFeatures.None)">Reject</button>
    </div>
}

@code {
    private bool _ready;

    protected override void OnInitialized() => Consent.Changed += OnChanged;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender is false) return;

        await Consent.RefreshAsync();
        _ready = true;
        StateHasChanged();
    }

    private void OnChanged(object? sender, EventArgs e) => _ = InvokeAsync(StateHasChanged);

    public void Dispose() => Consent.Changed -= OnChanged;
}

Outside .NET - on a static SSR page, or in plain JavaScript - the script has the same API:

API

IBelemetryConsent Interface

Reads and changes the user's consent at runtime, per BelemetryFeatures - the API behind a consent banner or a privacy settings page.

Name Type and default Description
Available BelemetryFeatures The features that can be consented to - the configured BelemetryOptions.Features.
Granted BelemetryFeatures The features currently granted - always a subset of IBelemetryConsent.Available, so these are collected.
IsDecided bool Whether the user has made a choice (or one was restored from a previous visit).
Changed EventHandler Raised after the granted features or the decided state changed.
IsGranted(BelemetryFeatures feature) bool Whether every flag of feature is granted.
SetAsync(BelemetryFeatures features) ValueTask Records the user's decision: exactly features (those of them that are IBelemetryConsent.Available) are granted, everything else is revoked (queued data of revoked features is discarded, and their device storage cleared).
GrantAsync(BelemetryFeatures features) ValueTask Grants features in addition to what is granted.
RevokeAsync(BelemetryFeatures features) ValueTask Revokes features, keeping the rest.
RefreshAsync() ValueTask Reloads the state from the browser (e.g. after the first interactive render on Blazor Server).

BelemetryFeatures Enum

The independently consentable parts of Belemetry. Every telemetry record belongs to exactly one feature, and a record is only sent while its feature is both enabled by configuration and granted by the user.

Value Number Description
None 0 Nothing.
Errors 1 JS errors and unhandled rejections, .NET logs at Error/Critical or carrying an exception, and TrackException.
PageViews 2 Page views - history and enhanced navigation, and TrackPageView.
Interactions 4 Clicks and other user interactions.
Performance 8 Web vitals (LCP, INP, CLS, FCP, TTFB), the document-load span, resource timing and long tasks.
Dependencies 16 Browser fetch/XHR client spans.
Sessions 32 A session id that survives page reloads (kept in sessionStorage). Without it the session id lives in memory only.
UserTracking 64 The pseudonymous user id (enduser.pseudo.id, persisted on the device) and the authenticated user id (user.id).
Logs 128 .NET logs below Error, and browser console capture below error.
Traces 256 .NET Activity spans.
Metrics 512 .NET Meter metrics.
Persistence 1024 Keeping unsent telemetry on the device (browser storage or files) to retry it after going offline or restarting.
Events 2048 Custom events (TrackEvent).
Diagnostics 401 Errors, logs, traces and dependencies - what is needed to troubleshoot the app.
Analytics 2150 Page views, interactions, sessions, user tracking and custom events - what describes how the app is used.
Monitoring 520 Web vitals and metrics - how the app performs.
All 4095 Every feature.

BelemetryConsentOptions Class

How consent is obtained and kept.

Name Type and default Description
Default BelemetryFeatures= BelemetryFeatures.None The features granted before the user has decided. Defaults to BelemetryFeatures.None (opt-in: nothing is sent until consent is given). Set it to e.g. BelemetryFeatures.Errors when error reporting is covered by legitimate interest, or to BelemetryFeatures.All for apps that do not need consent at all.
Persist bool= true Whether the user's choice is remembered on the device (localStorage key bit-belemetry-consent, which records the choice itself and is therefore strictly necessary). Defaults to true.
HonorGlobalPrivacyControl bool= true Whether a browser signalling Global Privacy Control (navigator.globalPrivacyControl) never gets the BelemetryFeatures.Analytics features, whatever is requested. Defaults to true.
PendingLimit int= 500 How many records of not-yet-granted features are held in memory while the user has not decided, to be sent if granted and dropped if not. Defaults to 500.
An unhandled error has occurred. Reload 🗙