Every visitor sees the same consent banner and the same tags fire regardless of jurisdiction — a Frankfurt user gets an opt-out notice that CCPA permits but GDPR forbids, and a California user is blocked from a sale-of-data toggle they are entitled to. The single point of failure is almost always a missing or misordered OneTrust Geolocation Rule that never maps the resolved region to a published consent template.
Triage: Confirm the Same Template Renders in Every Region
Before touching the OneTrust admin console, prove that the banner is region-blind and identify which template is being served everywhere.
- Open Chrome DevTools → Application → Local Storage and delete every
OptanonConsentandOptanonAlertBoxClosedkey so the CMP re-evaluates from scratch. - In the Console, read the geolocation OneTrust resolved for this session:
// OneTrust writes the resolved location into a global after the banner loads.
// OptanonGeolocation is set by the geolocation script (geolocation.json call).
console.log('Resolved region:', window.OptanonGeolocation);
// The active ruleset / template id is exposed on the OneTrust object:
console.log('Active group data:', window.OneTrust.GetDomainData().Groups.map(g => ({
id: g.OptanonGroupId, // C0001, C0002, C0004 ...
name: g.GroupName,
status: g.Status // "active" / "always active" per template
})));
- Switch your egress region with a VPN (or override — see the Resolution section) to an EU IP, reload, and re-run the snippet. If
window.OptanonGeolocationchanges but theGroupsarray and theirStatusvalues do not, the geolocation rule is resolving the country yet no rule maps that country to a different template. - Open the Network tab, filter for your vendor domains (
google-analytics.com,connect.facebook.net,doubleclick.net) and confirm the same requests fire on both the EU and US loads. Identical requests across jurisdictions is the reproducible symptom.
Reproduction checklist:
Root Cause: No Rule Maps Region → Consent Template → Script Routing
OneTrust resolves a visitor’s country and state from their IP on every page load and exposes it as window.OptanonGeolocation. But resolving a location does nothing on its own. A Geolocation Rule is the object that binds a set of locations to a specific published consent template (banner + preference centre + default group statuses). Without a rule that names your regions, OneTrust falls through to the domain’s default template, so every visitor — Berlin, Los Angeles, or São Paulo — receives whatever behaviour that one template encodes.
The routing failure is three missing links in a chain:
- No location grouping. The EU/EEA countries, US privacy-law states, and everywhere-else are not collected into distinct Geolocation Rule groups, so there is nothing to branch on.
- No template binding. Even where a location group exists, it is not pointed at a template whose group defaults encode the correct legal basis — opt-in (default inactive) for GDPR versus opt-out (default active) for CCPA.
- No routing read. The application injects tags from a static list rather than reading the resolved OneTrust groups, so the CMP’s decision never reaches the tag loader.
This is the concrete, vendor-specific implementation of the model described in the regional routing for CCPA and global privacy laws guide, and it depends on the jurisdiction-to-rule translation covered in mapping regional privacy laws to script routing rules. The legal basis determines the default group status the template ships; the geolocation rule determines which template a visitor gets; and the routing read turns the resolved groups into actual <script> injections.
Resolution: Build Ordered Geolocation Rules and Read Resolved Groups
The fix has two halves: the OneTrust admin configuration that produces distinct resolved groups per region, and the client-side router that turns those groups into script injections.
Step 1 — Create location groups and publish three templates
In the OneTrust admin console under Geolocation Rules, create a rule group per legal regime rather than per country. Each rule binds a location set to a published template:
- Rule 1 — EU/EEA + UK → GDPR opt-in template. Location set: all EEA member states plus the UK, Switzerland. Template: a banner with categories
C0002(Performance),C0003(Functional),C0004(Targeting) shipped inactive by default, requiring an explicit accept. - Rule 2 — US privacy states → CCPA/CPRA opt-out template. Location set: California, Colorado, Connecticut, Virginia, Utah, plus the newer state laws. Template: the same categories shipped active by default, with a “Do Not Sell or Share My Personal Information” link that flips
C0004. - Default rule → no-banner template. Everywhere with no applicable law: all groups active, no banner (or a lightweight notice).
Order matters. OneTrust evaluates Geolocation Rules top-down and stops at the first match, so the most specific location sets must sit above broader ones. If a broad “North America” rule sits above the “US privacy states” rule, California resolves to North America first and never reaches the opt-out template. Drag the state-level rule above any country- or continent-level rule.
Step 2 — Bind the loader to data-domain-script
The single OneTrust CDN tag is what pulls the resolved template. The data-domain-script attribute is the domain UUID; append -test in staging to exercise draft templates without publishing:
<!-- Load OneTrust before any consent-gated vendor tag.
The domain UUID resolves to the geolocation-selected template at runtime. -->
<script
src="https://cdn.cookielaw.org/scripttemplates/otSDKStub.js"
type="text/javascript"
charset="UTF-8"
data-domain-script="01920000-0000-0000-0000-000000000000"></script>
<!-- OptanonWrapper runs after the SDK has resolved geolocation + template -->
<script type="text/javascript">
function OptanonWrapper() {
// Fires on load AND on every consent change — the routing hook.
window.dispatchEvent(new CustomEvent('ot-consent-resolved'));
}
</script>
Step 3 — Read the resolved groups and route scripts
The routing read is the piece that most implementations skip. Instead of hard-coding which tags load, ask OneTrust which groups the resolved template activated, then inject only those vendors. OnetrustActiveGroups is a comma-delimited string of the currently-consented group IDs; OneTrust.IsAlertBoxClosed() confirms the banner has been resolved.
// A declarative map of vendor tags to the OneTrust category that gates them.
const SCRIPT_ROUTES = [
{ group: 'C0002', src: 'https://www.googletagmanager.com/gtag/js?id=G-XXXX' }, // Performance
{ group: 'C0004', src: 'https://connect.facebook.net/en_US/fbevents.js' }, // Targeting
];
function routeScriptsForRegion() {
// OnetrustActiveGroups looks like ",C0001,C0002,C0004," — pad-delimited.
const active = window.OnetrustActiveGroups || '';
for (const route of SCRIPT_ROUTES) {
const permitted = active.includes(`,${route.group},`);
const already = document.querySelector(`script[data-ot-src="${route.src}"]`);
if (permitted && !already) {
const s = document.createElement('script');
s.src = route.src;
s.async = true;
s.dataset.otSrc = route.src; // idempotency marker
s.dataset.otGroup = route.group; // for teardown on revocation
document.head.appendChild(s);
}
}
}
// Run on initial resolution and on every subsequent consent change.
window.addEventListener('ot-consent-resolved', routeScriptsForRegion);
Because OptanonWrapper() re-fires on every consent change, a Berlin visitor who accepts targeting later triggers the same router and injects the Facebook tag on the spot — while that same tag ships already-active for a California visitor until they use the opt-out link. One code path, three jurisdictional outcomes, all driven by the resolved groups rather than the visitor’s raw country. For the teardown side of a later opt-out, hand the data-ot-group marker to the consent-syncing teardown across vendors logic.
To exercise a region without a VPN during development, override the resolved geolocation before the SDK stub loads:
// DEV ONLY — force the SDK to resolve as if the visitor were in California.
// Remove before production; overriding geolocation in prod is a compliance risk.
window.OneTrust = window.OneTrust || {};
window.OptanonGeolocation = 'US;CA';
Verification
Load the page from two egress regions (or with the dev override above) and diff the routing outcome:
// Run in Console on each region load, then compare the two JSON blobs.
JSON.stringify({
region: window.OptanonGeolocation,
activeGroups: window.OnetrustActiveGroups,
injected: [...document.querySelectorAll('script[data-ot-src]')]
.map(s => s.dataset.otGroup)
});
A correct configuration returns different activeGroups and injected arrays across regions: the EU load shows C0001 only until consent (no C0004 script node), while the US load shows C0001,C0002,C0004 with the Facebook tag already injected. Confirm in the Network tab that connect.facebook.net fires on the US load and is absent on the pre-consent EU load. That divergence is the single observable proof the geolocation rule now routes scripts per jurisdiction.
Common Pitfalls
- Rule order inverted. A continent- or country-level rule placed above a state-level rule swallows the match, so California resolves to a generic US or North America template and never reaches the CCPA opt-out behaviour. Always order most-specific-first; OneTrust stops at the first hit.
- Routing from raw country instead of resolved groups. Reading
OptanonGeolocationand branching in your own code duplicates the template logic and drifts out of sync the moment a compliance owner edits a template. Route fromOnetrustActiveGroups, which is the CMP’s authoritative decision. - Ignoring the re-fire of
OptanonWrapper(). Treating it as a one-time load hook means a visitor who changes consent gets no new tags injected (or stale tags left running). It fires on every change — make the router idempotent with adata-ot-srcmarker so repeat calls neither double-inject nor miss late grants.
Related
- Regional Routing for CCPA and Global Privacy Laws — the governing model this OneTrust configuration implements
- Mapping Regional Privacy Laws to Script Routing Rules — translating each jurisdiction into a legal-basis default
- Syncing Consent States Across Multiple Vendors — tearing tags down when a routed script’s consent is later withdrawn
- Architecting GDPR-Compliant Consent Gating — the opt-in state machine the EU template depends on