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.
Branded footer settings
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.
Connect your consent manager
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.
Same-page Meta cookie observation
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.