Skip to content

Gate analytics behind an explicit Accept All click#365

Merged
swapnil-cometchat merged 4 commits into
mainfrom
docs/consent
May 19, 2026
Merged

Gate analytics behind an explicit Accept All click#365
swapnil-cometchat merged 4 commits into
mainfrom
docs/consent

Conversation

@swapnil-cometchat
Copy link
Copy Markdown
Contributor

@swapnil-cometchat swapnil-cometchat commented May 19, 2026

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 — remove integrations.gtm. Mintlify's auto-injection of GTM-59ZJRV2 ran 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 as assets/version-aligner.js). Does four things:

  1. Sets Google Consent Mode v2 defaults to denied for ad_storage, ad_user_data, ad_personalization, analytics_storage before anything else runs.
  2. Renders an own-brand cookie banner (fixed bottom bar, verbatim HubSpot copy, Accept All + Decline All, dark-mode aware, role="dialog", mobile-responsive under 880px). HubSpot's stock banner is suppressed via CSS keyed on html[data-cc-consent] so users never see a double-prompt after GTM eventually loads.
  3. Does not load GTM at all on page load. GTM is injected inline only after Accept All is clicked, which is the only state that produces requests to googletagmanager.com or google-analytics.com.
  4. Persists the choice in 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 collect request 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 to google-analytics.com regardless of gcs. So the only posture that closes the exposure is "don't load GTM at all until Accept."

Verified on preview

Step Result
Fresh incognito, load preview ✅ banner visible, 0 requests to GTM/GA
Click Accept All ✅ banner disappears, gtm.js?id=GTM-59ZJRV2 loads, collect?…&gcs=G111&… fires
Fresh incognito, click Decline All ✅ banner disappears, 0 GTM/GA requests on this page or subsequent navigation
Returning visitor (prior accept) ✅ no banner, GTM/GA fire immediately on load
localStorage.clear() + refresh ✅ banner re-shows

Out of scope (handled separately)

  • cometchat.com (marketing site) — same GTM-59ZJRV2 container, same exposure. Tracked separately by the marketing team.
  • Long-term centralized fix in the GTM workspace (Consent Mode v2 + CMP) — would replace this client-side gate and cover both properties at once.

🤖 Generated with Claude Code

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>
@mintlify
Copy link
Copy Markdown

mintlify Bot commented May 19, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
cometchat 🟢 Ready View Preview May 19, 2026, 6:13 AM

💡 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>
@swapnil-cometchat swapnil-cometchat changed the title Disable GA until cookie consent is wired up Gate Google Analytics on cookie-consent acceptance May 19, 2026
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>
@swapnil-cometchat swapnil-cometchat changed the title Gate Google Analytics on cookie-consent acceptance Gate analytics behind an explicit Accept All click May 19, 2026
@swapnil-cometchat swapnil-cometchat merged commit fea848b into main May 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants