Consent · engineers · marketers · PMs
Consent Mode is a gtag signal, not a CMP
Google needs to know whether it may use cookies and whether it may use user data. Consent Mode v2 adds ad_user_data and ad_personalization. A banner that never calls consent update still ships whatever default you set.
Four keys
Consent Mode v2 is four storage and data flags: ad_storage, analytics_storage, ad_user_data, and ad_personalization. Each is granted or denied. v1 had the two storage keys. v2 adds the two data keys Google asked for in the EEA. Missing the v2 keys on EEA traffic is a Google Ads and Analytics policy problem. It is not a Pixellint core privacy rule. Core checks IAB strings on URLs, not gtag's in-page consent state.
A default of granted with no update is how you ignore GDPR and still tell yourself you installed Consent Mode. A default of denied that never updates is how you installed it and then never measured. Pick the defaults with legal, then wire the CMP callback to gtag('consent', 'update', ...). Two CMPs plus a hard-coded default is how ad_storage flips twice on one view.
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500
});
Default then update
Set denied defaults before GTM or gtag loads, then update on choice. Loading GTM first and denying later still leaked the first hits. The default snippet exists because of that race. In GTM, Consent Initialization has to fire before Container Loaded. Tags then declare which storage they need. gtag.js without GTM is the same contract with a different API.
wait_for_update gives the CMP a few hundred milliseconds to return before the default is treated as final. If the CMP is slow and the default is granted, you leaked. If the CMP is slow and the default is denied, the first hit is redacted, which is the point. Do not set wait_for_update to zero to make the page feel faster unless legal signed that sentence.
gtag('consent', 'update', {
ad_storage: 'granted',
analytics_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted'
});
Advanced versus basic
Basic: tags wait for grant. Advanced: tags fire in a cookieless or redacted mode and Google models conversions. Pick one with legal. Pixellint will not choose it for you. If legal wants no hit to Google until yes, you need basic, or you need the tag not to load.
Modeled conversions in Ads are not a bug in the pixel. They are the product of denied storage plus a conversion you still wanted to count. A conversion tag with no consent requirement will fire under the banner the same as it did in 2019. Check Consent Overview in GTM, not the modal screenshot. Check the collect URL after refuse and after accept. They should not be the same request.
Not a TC String and not Floodlight
Consent Mode is Google's API. TCF is IAB's string on the pixel URL. Floodlight still wants gdpr and gdpr_consent. A site can have both. Wiring only one of them is a regional hole. Do not assume a granted ad_storage is a TC String. Do not paste a TC String into a gtag consent call. Do not expect gtag to populate gdpr_consent, gpp, or gpp_sid.
A Meta pixel next to a consented gtag still needs its own signal, or its own hold. Consent Mode does not speak IAB. IAB does not speak Consent Mode. pixellint validate url on a Floodlight tag checks the IAB pair. It does not certify that gtag('consent', 'update') ran. Those are two artifacts.
https://ad.doubleclick.net/ddm/activity/src=1234567;type=convr0;cat=purch0;ord=8675309;gdpr=1;gdpr_consent=CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA?
What the Google packs actually check
The google-tag-manager pack checks the loader: id on googletagmanager.com. The google-analytics-collect pack checks v, tid, cid, and en on /g/collect. The google-ads-conversion pack checks conversion_id, label, guid, and script on the conversion image. None of those packs is a Consent Mode validator. Do not invent a Pixellint finding named ad_storage_denied.
Use those packs to confirm the tag that fired is the tag you meant. Use GTM Consent Overview and a refuse/accept QA pass to confirm Consent Mode. Use core privacy rules when the same page also fires Floodlight or another IAB hop. Three tools, three contracts. Mixing them in a ticket is how a granted collect ping gets blamed for a missing TC String.
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.