docs
    site-kit: Resonance measurement
    v8.12.0.md

    Resonance measurement

    @sonordev/site-kit/resonance connects the site's consent manager to scoped measurement receipts and approved advertising destinations. SiteKitLayout mounts its childless runtime alongside the existing analytics provider. Page content and managed forms still render when measurement is unavailable. The small shell blocks unresolved collection immediately; its controller loads after the page reaches idle or the visitor interacts.

    On dynamic routes with Next.js Cache Components, the layout gives the pathname-dependent runtime its own Suspense boundary. Measurement stays blocked while it's unresolved, and page content renders independently.

    First-party privacy preferences

    For a site without a consent manager, mount ResonanceConsentPreferences once alongside SiteKitLayout.

    import { ResonanceConsentPreferences } from '@sonordev/site-kit/resonance'
    
    <ResonanceConsentPreferences privacyUrl="/privacy" />

    Visitors can choose visit measurement and advertising separately, decline optional purposes, and reopen Privacy choices to withdraw. Both optional choices start off. The component uses the existing adapter registration and keeps an already registered consent manager in charge.

    A choice needs a fresh, enabled native configuration. The component waits while settings are unavailable. Stored preferences belong to the exact site, project, configuration and policy revisions. They expire after 180 days by default, with maxAgeDays configurable from 1 to 180. Native proof expiry is checked independently. If browser storage is unavailable, the choice lasts for the current tab. Withdrawal and cross-tab changes notify the existing runtime.

    This component records preferences through the native consent owner. Configure the real page rules, destinations and approved visit feed before verifying advertising delivery.

    Mount the same leaf in the footer to keep one preference owner. Existing callers retain the floating launcher and automatic dialog. The optional presentation props are launcherPlacement (floating or inline), launcherLabel, promptMode (dialog or panel), and className.

    <ResonanceConsentPreferences
      launcherPlacement="inline"
      launcherLabel="Privacy settings"
      promptMode="panel"
      className="workshop-consent"
    />

    Panel mode presents equal Accept optional, Reject optional and Customize actions when the native configuration is ready and no current preference exists. It opens without moving focus. Dismissal lasts for this mount and records no choice. Customize opens the existing native dialog. Closing returns focus to the initiating control, or the footer launcher if that control disappears after a choice. Unsaved settings remain visible when configuration becomes unavailable. Every save rechecks the owner, and a failed save stays open for review and retry. The saved message describes the local preference on this page.

    Style the root with inherited --sk-consent-surface, --sk-consent-foreground, --sk-consent-muted, --sk-consent-border, --sk-consent-accent, --sk-consent-focus, --sk-consent-radius, and --sk-consent-font tokens. The font token accepts a complete CSS font value, including inherit. Prompt placement uses --sk-consent-prompt-inset and --sk-consent-prompt-bottom. Stable slot classes are sk-consent__launcher, sk-consent__prompt, sk-consent__dialog, sk-consent__actions, and sk-consent__status.

    .workshop-consent {
      --sk-consent-surface: #18201e;
      --sk-consent-foreground: #fff8e8;
      --sk-consent-muted: #d8d0bf;
      --sk-consent-border: #8b806a;
      --sk-consent-accent: #f0cd84;
      --sk-consent-focus: #f0cd84;
      --sk-consent-font: inherit;
    }

    For a site using the shared Echo chat, --sk-echo-offset-bottom is the existing placement input for the launcher and popup. A narrow layout can reserve the 60px launcher plus a gap beneath the privacy prompt by setting its bottom token from that value, safe-area inset and --sk-vv-layout-gap. Keep the existing CTA owner in charge of writing the Echo offset.

    [data-sk-echo-panel] is the supported styling marker for an open Echo panel. At a narrow breakpoint, a site can hide only its nonmodal sk-consent__prompt while that marker exists, using a CSS :has() rule. The native privacy dialog remains independent. Verify chat open and closed states, short viewports, text zoom and focused content before shipping the placement.

    Register one adapter for the consent manager your site already uses. The adapter reads its actual current preferences and subscribes to changes. It doesn't create a banner or choose preferences for the visitor.

    'use client'
    
    import { useEffect } from 'react'
    import {
      registerResonanceConsentAdapter,
      type ResonanceConsentAdapter,
    } from '@sonordev/site-kit/resonance'
    
    // Map these methods to your site's maintained consent manager.
    export function ConsentConnection({ adapter }: { adapter: ResonanceConsentAdapter }) {
      useEffect(() => registerResonanceConsentAdapter(adapter), [adapter])
      return null
    }

    The adapter supplies requiredPurposes, read() and subscribe(onChange). read() may be asynchronous. It returns:

    {
      valid: true,
      observedAt: '2026-10-24T12:00:00Z',
      source: 'cmp', // or 'user_preference'
      choices: {
        analytics_storage: 'granted',
        ad_storage: 'denied',
        ad_user_data: 'denied',
        ad_personalization: 'denied',
      },
    }

    Each purpose can be unknown, granted, denied or withdrawn. Return valid: false when the manager can't establish current preferences. Include analytics_storage in requiredPurposes when your site's policy requires it. Notify subscribers as soon as a preference changes. Dispatch stops before an asynchronous read completes.

    The initial config read creates no measurement subject. A valid current preference observation can create a new subject with server receipts. Configured collection waits for that receipt response before activating. The opaque proof stays in memory and never enters a URL or browser storage. Its 30-minute renewal doesn't prompt the visitor again or change consent. After memory is lost, an actual valid preference observation can establish a new subject. Visitor IDs and old receipt IDs can't recover the previous subject.

    What can send?

    Advertising requires a current server proof and grants for ad_storage, ad_user_data and ad_personalization, plus any additional configured purpose. Explicit analytics denial stops first-party tracking. Required but unknown analytics consent also stops it. Legacy sites without a configured requirement keep their first-party analytics behavior, recorded as unknown policy rather than a consent grant.

    The existing analytics provider remains the pageview counter. Resonance occurrences supply separate retargeting and experiment evidence. Resource interest uses approved resource IDs. It doesn't submit raw query strings, form values or DOM text. Accepted forms become conversions on the server.

    Withdrawal clears pending occurrence retries and prevents delayed work from crossing into a later grant. Granting again records the current page with a new occurrence ID. It doesn't replay denied page history. Failed occurrence requests can retry with the same event ID while their scope and consent remain current. Already delivered provider requests can't be retracted.

    Advertising click lineage

    After the current advertising grants and scoped proof are available, a new page-view occurrence can carry one gclid, gbraid or wbraid from the current URL. The live pathname must match the navigation being recorded. Duplicate or mixed identifiers, whitespace, control characters and values longer than 1024 characters leave matching data absent. The length is an application transport bound. Other occurrence kinds don't carry click identifiers.

    This path works when analytics consent is denied and the configured advertising policy doesn't additionally require it. The runtime doesn't retain a query from before consent or recover one that's been removed. Retries use the same event within its current consent and scope; withdrawal clears them. An already recorded page isn't updated with a later identifier. No click identifier enters browser storage or public status output.

    An observed identifier doesn't prove a paid click. The server owns the link to the actual accepted form and checks qualification and current advertising consent before export. Deploy its accepting occurrence parser before enabling the browser change.

    After the required advertising grants and actual scoped consent proof are available, the existing page-view occurrence can include an SDK-reported metaCookieObservation. The runtime reads only actual _fbp and _fbc cookies from the current page. It doesn't construct fbc from a click URL, write cookies, retain a cookie jar, or install Meta's browser SDK.

    Matching requires a unique configured page resource for the current path. The occurrence uses that exact native resource reference and the same navigation's pagePath. Missing or ambiguous mappings leave the base view unmatched. Cookie reads don't run before persisted consent, for an unmatched page, or after consent/proof/config expiry. The live pathname must match before and after reading. Duplicate cookie names are omitted. The shared occurrence parser rejects invalid matching values; valid values contain 1–1024 Unicode characters without whitespace or controls. Invalid optional matching leaves the valid base occurrence intact.

    This enriches one existing page view. It doesn't add another page-view counter, create a business conversion or claim that a person rendered the page. Existing content-interest evidence remains separate. Retries retain the exact original event ID, navigation, time and captured body without reading cookies again. Repeated consent notifications or configuration-cache refreshes don't update an already recorded view. Withdrawal clears pending work; a later grant can record only the current page with a new event.

    Raw matching isn't forwarded through the provider event allowlist, public status or browser storage. The accepting API must support the occurrence shape and verify the actual owned page, current source rules and consent before enabling this browser version. An accepted capture doesn't grant a feed, prove native audience admission or establish provider delivery. Those checks and the original grant/claim/time fences remain server-owned.

    Provider capabilities

    Google's allowlisted tag loads after the required grants. Automatic pageviews are disabled. Browser events use an explicit safe page location, empty referrer and approved resource fields. The runtime has no browser conversion action. A pre-existing Google tag or tag manager blocks installation until compatibility is resolved, preventing a second installation.

    Meta browser pixel

    The browser pixel exists for one reason: so Meta's own first-party cookies (_fbp, and _fbc when a visitor arrives from a Meta ad click) exist on pages you've approved. It sends no browser events. The only Meta event for a visit is the server's Conversions API PageView, and the runtime exposes no track capability. It passes no user data to the pixel, reads no form field, page title or page metadata, and attaches no click or submit listener. It never writes _fbp or _fbc itself and never builds one from a click URL.

    It's switched off in this release. Site Kit 8.9.0 can't start the pixel. This build implements no loader controls revision, so even a capability witness from the server can't lift meta_browser_collection_unverified, and getResonanceMeasurementStatus() keeps reporting it. A later release turns the loader on. Everything below describes how it will behave then.

    When it's on, a site's pixel starts only if the server's configuration carries a capability witness for that site. The witness names the loader controls revision and the policy revision it was issued under, and the runtime refuses a revision it doesn't implement, a witness from an older policy, and a witness issued more than 48 hours ago, and it withdraws a running pixel if the witness lapses mid-session. A site without a witness loads nothing. The lift is per site, and a configuration refresh that removes the witness, the destination or enabled withdraws the pixel. The longest that can take is the configuration's own expiry.

    What loads, and when. Nothing exists before consent: no script, no window.fbq, no cookie, no storage key. After the three advertising grants (ad_storage, ad_user_data, ad_personalization) and a current scoped proof, and on a page that's eligible and clean, the runtime queues consent revoked, automatic configuration off and init with only the dataset ID from your signed configuration, then inserts https://connect.facebook.net/en_US/fbevents.js (async, no referrer). Once the script has run, and the consent fence still holds, it grants consent. Each step rechecks the fence. A grant that's queued ahead of the script doesn't release the pixel's own lock, which is why the grant waits.

    Which pages. A page is eligible only when its configured page resource has metaEligible: true, which a person with owner or admin standing sets in Sonor. It defaults to false, an agent can't set it, and the browser never decides it. The same flag gates the cookie observation described above. The runtime also refuses, with meta_page_not_eligible: any frame, a private preview, a local host, a Sonor application host, a page carrying data-sk-authenticated, and these paths and their children: /admin, /account, /dashboard, /login, /signin, /signup, /checkout, /cart, /portal, /api and /preview. Put data-sk-authenticated on the document in any layout that sits behind a login.

    Clean URLs only. The pixel reads the page URL and referrer on its own, so the runtime doesn't start it, and pauses it on a single-page navigation, unless the query holds only fbclid, sonor_source or utm_* keys with plain values, there's no fragment, and the referrer is empty, an origin-only cross-site address, or a same-site address whose path isn't on the denied list above and whose query follows the same rule. Otherwise the status shows meta_url_not_clean. There's no rewriting.

    One pixel per page. A page with its own fbq, a Meta script tag, a pixel image or noscript, or a tag manager gets meta_duplicate_installation and no second pixel. A tag manager clears only through an approved attestation in the signed configuration. A changed dataset ID or scope under a running pixel shows meta_scope_change_requires_reload, and more than one Meta destination shows meta_multiple_destinations. A script that's blocked or fails shows meta_load_failed. It isn't retried and no image or noscript fallback is added.

    Withdrawal. When consent, the proof or the configuration lapses, the runtime sets the pixel's consent to revoked, stops issuing commands and, once the pixel has run, expires _fbp and _fbc on the host and its parent domains, whether or not consent was ever granted on that page. Code that has already run can't be retracted, so the guarantee is that nothing further is sent. A later grant starts fresh and never replays an earlier visit.

    For your site. Your consent manager's vendor list and privacy notice must name Meta, and the Conversions API user agent has its own consent wording. Sites that ship a Content Security Policy need script-src for https://connect.facebook.net, plus connect-src and img-src for https://connect.facebook.net and https://www.facebook.com. Site-kit's securityHeaders emits none of these. First-party receipts alone don't prove provider audience admission, conversion matching or attribution.

    Read getResonanceMeasurementStatus() to inspect the current policy and provider blockers. advertisingPermitted describes consent eligibility. It doesn't establish provider delivery or an installed campaign.

    Managed experiments require the server's current assignment ID, experiment revision and site. Without that witness, the existing experiment component shows its control content. Its exposure reference records the assignment actually rendered. Forms remain usable with measurement denied or unavailable.

    Other layouts and verification

    A custom layout can mount <ResonanceRuntime site="example.com" ... /> as a childless sibling after publishing the existing browser-safe site credential. Use the established server credential component. Don't expose a raw API key. Mount one runtime per document.

    Verify unknown, grant and withdrawal states against your actual consent manager before enabling a campaign. Check preview suppression, navigation, scope changes and failed requests using intercepted provider URLs. A built or packed package isn't proof that a site installed it or that a provider accepted an event.

    Current config and public source markers

    Config freshness renews through a public read. An unchanged revision preserves current consent; it doesn't create another subject or prompt. A changed config or policy stops the previous scope and reads actual current preferences before establishing the new scope. A failed lookup can't turn a configured requirement into legacy permission. Advertising stops when proof or config expires.

    The runtime reads only the bounded sonor_source parameter for campaign source mapping. It forwards that marker on a current permitted occurrence. The server verifies its signature and approved plan/destination scope. Other query fields don't become measurement payload. A marker authenticates reviewed source mapping, not a paid click or causal result. A visit made before consent isn't replayed after the visitor has moved to another page.

    The actual managed form submit refreshes its optional proof at each attempt. Its accepted exposure reference comes from the measurement response. Analytics and advertising purposes remain separate, and canonical form conversion stays on the server. Compatible Google installations created by this runtime can be reused for the same project/site and exact tag set. Changed scope or unknown installations expose a blocker instead of initializing another tag.

    The controller registry is shared by status observations, rendered experiment exposures and per-attempt form context. These readers use the same current policy and proof; none creates a grant. Deferred transport execution rechecks its current permission fence before sending, including after loading or credential refresh.