A visitor accepts cookies on www.example.com, clicks through to shop.example.com, and is shown the banner again — while your analytics records two consent events for one person and your vendors initialise twice with different states.
Triage: Establish Where the Record Lives
The symptom has one of two causes, and a two-minute inspection separates them.
- Accept on the main host. Open Application → Storage and find where the decision was written: a cookie,
localStorage, or both. - Read the cookie’s Domain column. A value of
www.example.comis host-scoped;.example.comis shared across subdomains. - Navigate to the second host and inspect the same storage. If the record is absent, you have a scoping problem; if it is present but the banner still appears, you have a reading problem.
- Check whether the consent platform is configured with the same identifier on both hosts — many platforms scope their own record per configured domain.
Root Cause: Storage Scope Does Not Match the Consent Scope
Browser storage is partitioned in ways that rarely match how a business thinks about its site. localStorage is scoped to an exact origin, so www.example.com and shop.example.com have entirely separate stores with no mechanism to share between them. Cookies are more permissive: a cookie set with Domain=.example.com is sent to every subdomain, which is why the consent record belongs there rather than in local storage.
The complication is that a shared cookie is a wider disclosure than a host-scoped one. Every subdomain now receives the record on every request — including subdomains you do not control tightly, such as a status page on a third-party platform or a marketing microsite run by an agency. That is an argument for keeping the cookie small and non-identifying, not for abandoning the approach: a category bitmask and a policy version disclose nothing a visitor’s own decision has not already established, while a full consent record with an identifier is something you would rather not broadcast.
The second, subtler cause is platform configuration. Most consent platforms scope their stored record by the domain configured in their console, so two hosts registered as separate properties will each maintain their own decision no matter how your cookies are scoped. The platform adapter is where this surfaces, because it is the only place that knows which record is authoritative.
Resolution: One Domain-Scoped Cookie, One Reader
Write the decision to a domain-scoped cookie alongside whatever the platform stores, and have every host read that cookie first:
// consent-cookie.js — the shared record. Small, non-identifying, versioned.
const KEY = 'consent';
export function writeConsent(state, policyVersion) {
const value = JSON.stringify({ v: policyVersion, c: state });
// Domain must start with a dot's modern equivalent — a bare apex domain,
// which browsers treat as covering subdomains.
document.cookie = [
`${KEY}=${encodeURIComponent(value)}`,
'Domain=example.com',
'Path=/',
'SameSite=Lax',
'Secure',
`Max-Age=${60 * 60 * 24 * 180}`,
].join('; ');
}
export function readConsent(currentPolicyVersion) {
const raw = document.cookie.split('; ').find((c) => c.startsWith(KEY + '='));
if (!raw) return null;
try {
const { v, c } = JSON.parse(decodeURIComponent(raw.split('=')[1]));
// A record captured under an older policy is not consent to the current one.
return v === currentPolicyVersion ? c : null;
} catch { return null; }
}
The version check is the part that is easy to omit and expensive to omit. Without it, a policy change — a new vendor category, a changed purpose — silently inherits consent that was given for a different set of processing, which is the stale-record failure an audit is most likely to find.
Where the consent platform maintains its own record, treat the cookie as the shared source and reconcile on load: read the cookie, and if it disagrees with the platform’s state, push the cookie’s value into the platform rather than the reverse. The cookie is the thing both hosts can see.
Verification: Accept Once, Navigate, No Banner
Clear all storage, accept on the first host, and navigate to the second. The banner must not appear, the vendors must initialise with the accepted categories, and the Application panel should show one cookie with Domain=example.com rather than two host-scoped records.
Why the Cookie Should Stay Small
There is a standing temptation to put more in the shared record — the full platform payload, a visitor identifier, a timestamp with a session reference — because it is convenient to have it available everywhere. Resist it, for two reasons that compound.
The first is disclosure. A domain-scoped cookie is transmitted to every subdomain on every request, including ones operated by third parties on your behalf. Anything in it is effectively shared with those operators whether or not they need it, and an identifier in particular converts a consent record into a tracking mechanism — which is an unfortunate thing to discover in a privacy review of your consent implementation.
The second is size. Cookies are sent on every request to every matching host, so a large record is a per-request tax on your own origin as well as a disclosure. A bitmask and a version fit comfortably in a few dozen bytes; a serialised platform payload can run to several hundred, on every asset request that does not have Domain scoping of its own.
Keep the detailed record wherever the platform puts it, per host, and let the shared cookie carry only what both hosts must agree on: which categories were granted, and under which policy version.
- Widening the cookie without auditing the subdomains. Enumerate what
example.comactually resolves to before setting an apex-scoped cookie; the list is usually longer than anyone remembers.
Common Pitfalls
-
Setting
Domainto a host rather than the apex.Domain=www.example.comcoverswwwand its own subdomains, not siblings. The apex is what shares across siblings. -
Relying on
localStoragewith a postMessage bridge. It works, requires an iframe on every page, and fails entirely under storage partitioning. The cookie is simpler and more durable. -
Omitting the policy version. A record without one cannot be invalidated when the policy changes, so an old decision silently authorises new processing.
-
Assuming the platform’s own cookie is shareable. Most consent platforms scope their record to the property configured in their console, so widening your cookie does not widen theirs. Reconcile explicitly on load rather than expecting the platform to notice.
Frequently Asked Questions
Does this work across entirely different domains?
No, and increasingly it cannot. Cookies cannot be shared between unrelated registrable domains, and the mechanisms that used to bridge them — a third-party iframe holding shared storage — are blocked or partitioned by every current browser. A consent decision on example.com genuinely does not transfer to example.net.
Treat separate domains as separate consent contexts and ask again, which is also the position most regulators take: consent is given in relation to a specific controller and context, and inferring it across properties a visitor may not associate with each other is difficult to defend. Where the properties are genuinely one service, consolidating them onto subdomains of one domain solves the problem properly.
What about a subdomain we do not control?
A domain-scoped cookie is sent to it regardless, which is a reason to audit the list of subdomains before widening the scope. A marketing microsite on an agency’s platform, a status page, a legacy host nobody has decommissioned — all of them will receive the consent record on every request.
If any of those are uncomfortable, the mitigation is the same one as for size: put nothing in the cookie you would mind them seeing. A record saying “analytics and marketing were accepted under policy 4” discloses a decision the visitor made about your site, which those hosts arguably ought to honour anyway.
Should the server read this cookie too?
It is worth doing, and it is the main practical benefit of using a cookie rather than any other mechanism. Because the cookie arrives with the request, a server-rendered page can emit the correct state in its initial HTML — no banner for a returning visitor who accepted, no layout shift when the gated content appears, and correct consent defaults inlined before any container boots.
Treat it as a hint rather than as authority. The client-side platform remains the source of truth because it is the only thing that can observe a change made in another tab, so the server’s job is to render the likely state and let the client correct it if it has since changed.