The Problem
A client opened their GA4 Conversions report on Monday and the line was flat for a week. Looking at the same window in their Stripe dashboard, there were 240 paid conversions. Real-time GA4 was firing events. The "Purchase" conversion event was active. But Conversions and the linked Google Ads import showed almost zero. Their CMP (Cookiebot) had been updated five days earlier, which lined up exactly with when the data dropped.
If your GA4 conversions chart suddenly went flat or fell 60-90% after a Consent Mode v2 deployment, this is the wave of broken implementations I have been cleaning up since the EEA enforcement deadline. Here is what is actually breaking and the snippets that fix it.
Why It Happens
Consent Mode v2 changed two things over v1. It added two new signals — ad_user_data and ad_personalization — and it made the default state of all four signals matter for whether GA4 will model the missing data.
The four signals are ad_storage, analytics_storage, ad_user_data, and ad_personalization. GA4's behavioural and conversion modelling requires that every page load fires a gtag('consent', 'default', ...) call before any GA4 measurement command, and that the default state is set to denied for visitors in scope (EEA, UK, Switzerland) until the CMP updates it.
Three failures cause the flat-conversions report.
Failure 1: Default consent fires after gtag config. Most CMPs inject their script asynchronously. If gtag('config', 'G-XXXX') runs before gtag('consent', 'default', ...), GA4 records the first hit with implied "granted" defaults, then the CMP updates the consent later, and the modelling pipeline marks the session as "non-compliant" and excludes it from conversion modelling.
Failure 2: ad_user_data and ad_personalization were never set. A v1 implementation only sets ad_storage and analytics_storage. The two new v2 signals stay undefined, which Google treats as "no consent provided." Conversions tied to Google Ads import drop because the import requires ad_user_data: granted to attribute the click.
Failure 3: ads_data_redaction and url_passthrough are off. When a visitor denies consent, Consent Mode v2 still pings /g/collect with a redacted cookieless ping — but only if ads_data_redaction: true is set. Without it, the deny path either sends nothing or sends a non-redacted hit that fails server-side validation. Either way the conversion modelling sample size is too small and Google stops modelling.
There is a quieter fourth failure on Single Page Apps and Next.js App Router builds: the consent default fires once on initial load but client-side route changes do not re-emit it, so deeply linked landing pages that hydrate before the CMP loads end up in the "no default" bucket and never get modelled.
The Fix
Step 1: Set the v2 default consent before anything else. This snippet must be the first script in <head>, before GTM, before GA4, before the CMP loader.
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// Default to denied for EEA, UK, CH; granted elsewhere.
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500,
'region': ['EEA', 'GB', 'CH']
});
gtag('consent', 'default', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted',
'analytics_storage': 'granted'
});
gtag('set', 'ads_data_redaction', true);
gtag('set', 'url_passthrough', true);
</script>
The two default calls do not conflict. Google evaluates the regional one first, then falls through to the global one for everywhere not listed. wait_for_update: 500 gives the CMP 500ms to call gtag('consent', 'update', ...) before the first hit fires.
Step 2: Update consent only on actual choice, not on page load. When the visitor accepts:
<script>
gtag('consent', 'update', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted',
'analytics_storage': 'granted'
});
</script>
Most CMPs (Cookiebot, OneTrust, Iubenda) have a built-in template that does this. Verify in DevTools' Network tab that the consent update event fires after the visitor clicks Accept, not on page load. If it fires on load, the CMP is auto-updating and you have effectively no consent gate.
Step 3: Re-emit consent on client-side route changes (Next.js App Router). Add a small client component that runs once on mount and republishes the consent state on every route change:
'use client'
import { usePathname } from 'next/navigation'
import { useEffect } from 'react'
declare global {
interface Window { dataLayer: unknown[] }
}
export function ConsentReemit() {
const pathname = usePathname()
useEffect(() => {
if (typeof window === 'undefined') return
window.dataLayer = window.dataLayer || []
window.dataLayer.push({ event: 'consent_check', path: pathname })
}, [pathname])
return null
}
Drop <ConsentReemit /> into your root layout. This pushes a consent_check event into the dataLayer on every navigation, which lets your CMP-aware GTM tag re-fire the consent state and keeps modelling eligibility intact across SPA navigations.
Step 4: Verify in the GA4 DebugView and Tag Assistant. Install the Google Tag Assistant, open your site, and watch the consent state column. You want to see all four signals listed with explicit denied or granted values on the very first GA4 hit of the session. If any signal shows undefined, your default call fired too late.
In GA4 itself, go to Admin → Property Settings → Data Collection → Consent Settings and confirm "Enable consent mode" is on. Then check Reports → Realtime → User properties and look for the consent state to flow through within a few minutes of the test traffic.
Step 5: Wait 48 hours, then check Modelled vs Observed. Behavioural modelling needs at least 1,000 daily users in the deny path before it starts back-filling. Once you cross that threshold, GA4 will rebuild the conversions chart from the modelled data and the line returns. If after 72 hours you are still flat, your ads_data_redaction flag is not making it through; recheck Step 1.
The Lesson
Consent Mode v2 conversion drops are almost always a load-order problem, not a CMP problem. Default-deny call fires first, both new v2 signals are explicitly set, ads_data_redaction is on, and SPA route changes re-emit consent. Get those four right and the conversion line refills within two days.
If your GA4 conversions, Google Ads ROAS, or attribution reports went flat after a CMP update and you need it diagnosed and fixed before the next reporting cycle, that is exactly what I do — see my services. If your INP score also looks worse since the CMP injected its scripts, I wrote the INP regression from GTM third-party tags fix which usually overlaps with this one.
