Gate analytics behind an explicit Accept All click#365
Merged
Conversation
Mintlify's integrations.gtm injection fires GTM (and GA4) on every page load with no consent check, before the existing cookie banner can be shown or interacted with. This is an immediate kill switch in response to a California privacy notice; GTM will be re-introduced via a Mintlify custom-script that wires Google Consent Mode v2 to the HubSpot banner, so GA only fires after the user clicks Accept All. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Mintlify auto-injects any .js file in the content tree on every page (same mechanism that loads assets/version-aligner.js). This file is the sole loader of GTM-59ZJRV2 now that `integrations.gtm` is removed from docs.json, and it does three things before GTM is fetched: 1. Sets Google Consent Mode v2 defaults to `denied` for all four storage types so GA4 (G-M5KZ2NFCYL) cannot fire `collect` requests until the user grants consent. 2. Restores any previously-saved choice from localStorage so returning visitors do not re-see the banner act as a tracking gate. 3. Hooks the HubSpot cookie banner via `_hsp.addPrivacyConsentListener` (HubSpot's documented privacy API) and pushes consent updates on Accept All / Decline All / X. GTM is then loaded inline at the end of the IIFE so ordering is guaranteed regardless of where Mintlify positions the script tag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verifying the preview revealed that HubSpot's _hsp.addPrivacyConsentListener fires immediately on registration with HubSpot's *implicit* consent state. On domains where HubSpot decides no banner is needed (mintlify.app preview, regions without consent requirements, anywhere not in HubSpot's banner-required list), that implicit state is allowed:true — so the previous bridge auto-flipped to granted with no user action, no banner shown, and GA fired with gcs=G111. Replace with capture-phase DOM event delegation matching the banner's known selectors (#hs-eu-confirmation-button, #hs-eu-decline-button) and visible text labels. Consent is now strictly tied to user intent: - If banner shows and user clicks Accept All → consent granted, GA fires. - If banner shows and user clicks Decline All → consent stays denied. - If banner does not show at all → no click, no grant, GA never fires. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strict-mode rewrite. Zero requests to analytics.google.com,
googletagmanager.com, or any GTM-loaded tracker fire on page load.
GTM is only injected after the user clicks Accept All on a banner
rendered by this script.
Flow:
- First visit: gtag default-deny is set, then a fixed bottom-bar
banner is rendered with verbatim HubSpot copy + Accept All /
Decline All buttons. No analytics scripts load.
- Accept All: gtag('consent','update','granted'), GTM loads inline,
choice persisted as localStorage.cc_consent='granted',
document.documentElement[data-cc-consent='granted'].
- Decline All: gtag('consent','update','denied'), GTM never loads,
choice persisted as localStorage.cc_consent='denied'.
- Return visit: prior choice restored from localStorage; no banner.
GTM loads only if previously granted.
HubSpot's own banner is suppressed via a CSS rule keyed on the
documentElement[data-cc-consent] attribute, so once the user has
made a choice HubSpot can't double-prompt them after GTM loads it.
Banner UI is plain DOM + inline CSS, scoped under #cc-consent-banner,
with dark-mode support via prefers-color-scheme. No external CSS or
fonts. role=dialog + aria-label for accessibility. Mobile-friendly
single-column layout under 880px viewport.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PrajwalDhuleCC
approved these changes
May 19, 2026
raj-dubey1
approved these changes
May 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stops every analytics request —
google-analytics.com/g/collect,googletagmanager.com/gtm.js, HubSpot tracking, PostHog ingest — from firing on docs.cometchat.com until the visitor explicitly clicks Accept All on a cookie banner.Triggered by a California privacy notice flagging GA hits before consent.
What this PR ships
docs.json— removeintegrations.gtm. Mintlify's auto-injection ofGTM-59ZJRV2ran on every page load with no consent check.assets/consent.js— new file, auto-injected on every page via Mintlify's content-tree convention (same mechanism asassets/version-aligner.js). Does four things:deniedforad_storage,ad_user_data,ad_personalization,analytics_storagebefore anything else runs.role="dialog", mobile-responsive under 880px). HubSpot's stock banner is suppressed via CSS keyed onhtml[data-cc-consent]so users never see a double-prompt after GTM eventually loads.Accept Allis clicked, which is the only state that produces requests togoogletagmanager.comorgoogle-analytics.com.localStorage.cc_consent. Returning visitors get their saved state restored — no banner re-show, and GTM loads only if they previously granted.Why "strict mode" and not Google's cookieless ping
Google Consent Mode v2 in default-deny state still sends one stripped-down
collectrequest per page view (gcs=G100, no cookies, no client ID). Google considers that consent-mode-compliant. The scanning tool that produced the legal notice does not — it flags any request togoogle-analytics.comregardless ofgcs. So the only posture that closes the exposure is "don't load GTM at all until Accept."Verified on preview
gtm.js?id=GTM-59ZJRV2loads,collect?…&gcs=G111&…fireslocalStorage.clear()+ refreshOut of scope (handled separately)
cometchat.com(marketing site) — sameGTM-59ZJRV2container, same exposure. Tracked separately by the marketing team.🤖 Generated with Claude Code