Since March 2024, Google requires Consent Mode v2 for EU/EEA traffic. Every major CMP (Cookiebot, OneTrust, Usercentrics, Iubenda, Didomi…) updated their consent banners to comply. Every analytics engineer added a gtag('consent', 'default', {…}) call. Every client was told the setup was fixed.
Most of those setups are still broken. Not because Consent Mode v2 is hard — but because the audit tools used to verify it are checking only 2 of the 4 required signals.
The 4 signals Google actually requires
Consent Mode v2 has four separate consent signals:
ad_storage— can Google store ad-related cookies?analytics_storage— can Google store analytics cookies?ad_user_data— can Google use user-provided ad data?ad_personalization— can Google use audience data for personalization?
The first two existed in Consent Mode v1. They are encoded in thegcs parameter every GA4 request carries — a compact 3-bit string like G100 where the bits represent (version, ad_storage, analytics_storage).
The other two were added for v2. They do not fit in gcs. They are carried in a separate parameter: gcd. Example: 11l1l1l1l5 where each lmeans "denied."
Why most audits miss it
When an audit tool reads a GA4 request and sees gcs=G100, it concludes "ad_storage and analytics_storage are both denied — Consent Mode v2 is working." That is only half true.
A site can have gcs=G100 AND gcd=11l1l1t1l5. That t in the 3rd position means ad_user_data: granted. The site is sending user-identifying ad data to Google before the user has consented. Under the DMA and GDPR, that is exactly what Consent Mode v2 was designed to prevent.
If your audit tool reads only gcs, it will happily report this site as compliant.
How to verify all 4 signals
There are two reliable methods:
Method 1: Read google_tag_data.ics.entries
Google exposes the current consent state as a JavaScript object at window.google_tag_data.ics.entries. Open DevTools on any site with GA4, paste this in the console before accepting the cookie banner:
Object.fromEntries(
Object.entries(google_tag_data.ics.entries).map(
([k, v]) => [k, v.default ? 'granted' : 'denied']
)
)A fully-compliant Consent Mode v2 setup returns:
{
ad_storage: 'denied',
analytics_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied'
}If any value is granted before the user has clicked accept, or if any of the 4 signals is missing entirely, your setup is not compliant.
Method 2: Inspect the gcd parameter
If JS state is unavailable (some sGTM setups strip it), read thegcd query parameter on any GA4 /g/collect request. The 4 letters after the leading version digits represent the 4 signals in order: ad_storage, analytics_storage, ad_user_data, ad_personalization.
Letter mapping:
t— grantedl— denied (user or default)r— denied by DMA regulationp,q,e— pending / not declared / deferred (effectively unknown)
A compliant pre-consent gcd looks like 11l1l1l1l5 or 13r3r3r3r5. Anything with a t in positions 3 or 4 is a red flag.
The partial-setup trap
Even more common than wrong values: the 4th signal is missing entirely. A site ships:
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
ad_user_data: 'denied',
// ad_personalization is never set
});This passes naive tools because all declared signals are "denied." But the missing signal defaults to granted in some CMPs and to unknownin Google's own logic, which Google Ads then treats as non-EEA-compliant and excludes the user from conversion modeling.
You lose compliance and attribution — the worst possible outcome.
DMA "denied by regulation"
One 2024+ addition worth knowing about: Google introduced the r letter to mark signals denied by the Digital Markets Act for EU/EEA users. If your CMP marks a user as being in the DMA region, the signals arrive as r instead of l. For compliance purposes, both mean denied, butr specifically tells Google to apply the DMA constraints even if the user tries to override them.
A good audit tool should report the regulation: "dma" state separately so you can confirm your EEA detection is working.
What a fully-compliant setup looks like
Minimum viable Consent Mode v2 defaults call:
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500, // ms to wait for CMP before tags fire
});Then the CMP calls gtag('consent', 'update', {…}) with the user's choices. All four signals must be present in both calls.
Run this check automatically
Our free scan runs exactly this validation. It reads google_tag_data.ics.entries via headless Chromium, parses the gcd parameter for every captured GA4 request, and flags any signal that is granted pre-consent or missing entirely. The same logic that caught production bugs on a dozen major EU sites during the development of our audit engine.