Cookie banner says Declined but tracking still loads: how to test and fix it

Cookie banner says Declined but tracking still loads: how to test and fix it

Updated: October 27, 2026
18 min read
Guide
Security auditor
Marketing strategist

On 22 October 2026 a Shopify merchant asked r/shopify how other store owners deal with tracking that keeps loading after a visitor clicks Decline. The banner said one thing and the browser did another. This is the most common cookie problem on any platform: the banner records a choice, but nothing on the page is wired to obey it. WooCommerce, Shopify, a headless Next.js or Astro storefront and a plain WordPress site all fail the same way, because the leaks come from embeds, fonts, theme code and tag configuration, not from the banner plugin itself.

This guide shows how to test what your site sends after Decline using only the browser, which requests usually slip through, what Google’s advanced consent mode still transmits by design, and how to gate each one. It cites the EU rules and the vendor documentation behind each point. It is a practical testing guide, not legal advice.

#Why tracking still loads after a visitor clicks Decline

The post that started the thread put the split clearly:

App pixels are easy. Shopify handles them. Everything else? YouTube videos, Google Fonts, chat widgets, SEO and analytics scripts: nope. In January 2022, a Munich court awarded a visitor €100 because a website passed the visitor’s IP address to Google through Google Fonts, without consent.

(u/Plus_Cell394, r/shopify, 22 October 2026)

A consent banner only controls what is registered with it. Anything hardcoded in the theme, pasted into a footer field, injected by an embed block or loaded by a plugin that ignores the banner’s API fires on page load regardless of the answer. The banner shows “Declined” and is correct about its own state. The page simply never asked it.

#What the EU rule actually covers: not only cookies

Article 5(3) of the ePrivacy Directive covers storing information on, or accessing information from, the visitor’s device. Two primary sources set its reach.

The Court of Justice ruled in Planet49 (C-673/17) that a pre-ticked checkbox is not valid consent, and that the rule applies “whether or not the information stored or accessed” is personal data. The EDPB Guidelines 2/2023 on the technical scope of Article 5(3), version 2.0 adopted on 7 October 2024, go further: JavaScript where the accessing entity “instructs the browser of the user to send asynchronous requests with the targeted information” falls within the scope. The guidelines have separate sections on tracking pixels, local processing and tracking based on IP address only.

So “we set no cookies” does not end the question. A script that reads localStorage or makes the browser ping a third party is in the same territory. The guidelines also note that the rule applying does not automatically mean consent is required, because exemptions exist. Whether one fits your case is a legal question. Whether the request happens at all is a technical one, and that is what you can test.

Use Chrome, Brave, Firefox or Edge. The steps are the same.

  1. Open a fresh profile or a private window with extensions off. Ad blockers hide exactly the requests you are looking for.
  2. Open devtools before loading the page. In the Network tab tick “Preserve log” and “Disable cache”.
  3. Load the home page and do not touch the banner. Note every third-party domain in the request list. Before any choice, nothing optional should load.
  4. Click Decline (or Reject all). Then click through a product page, a page with a video, the contact page and the cart. Leaks often sit on templates the home page does not use.
  5. Filter the request list by domain fragments: google, gstatic, doubleclick, facebook, tiktok, youtube, hotjar, clarity, the name of your chat vendor.
  6. Open the Application tab (Storage in Firefox) and check Cookies, Local storage and Session storage for each origin. Anything beyond the banner’s own record of the refusal and functional session cookies needs an explanation.
  7. Repeat with Accept to confirm the tools still work when they are allowed.

Write the results down as a table: domain, page, before choice, after Decline, after Accept. That table is your fix list.

#Which requests usually leak after Decline

SourceHow it shows up in NetworkTypical cause
Embedded YouTube or VimeoRequests to youtube.com, ytimg.com, googlevideo.com on page loadPlain iframe embed in content or a theme block
Google Fontsfonts.googleapis.com and fonts.gstatic.comTheme or plugin loads fonts from Google instead of local files
Chat widgetVendor script plus a websocket on every pageSnippet pasted into the footer or theme settings
Mapsmaps.googleapis.com, map tilesStore locator or contact page embed
Pixels hardcoded in the themeconnect.facebook.net, analytics.tiktok.com, Google Ads tagsCode pasted into header fields, outside the consent system
Tag manager tagsTags fire in GTM preview with consent deniedTags without consent settings, or triggers on All Pages

Google’s YouTube help page on embedding describes privacy-enhanced mode (youtube-nocookie.com) as preventing embedded views from influencing the viewer’s YouTube personalization. It does not say the embed sends nothing to Google, so swapping the domain is not a fix by itself.

Fonts are the case with a court ruling. The Munich Regional Court (LG München I, 3 O 17493/20, 20 January 2022) held that passing a visitor’s dynamic IP address to Google through Google Fonts without consent infringed the visitor’s rights, ordered the operator to stop, and awarded 100 euro in damages. The court reasoned that Google Fonts can be used without the browser connecting to a Google server. One commenter in the thread made the same point about scope:

Self-hosting fonts closes that one request.

(u/More_Initiative_9099, r/shopify, 22 October 2026)

Another reply pointed out that the issue is not only European:

This is also a problem in the US. Look up CIPA lawsuits.

(u/hurlbz, r/shopify, 22 October 2026)

Update, . The same leak sits on WooCommerce product pages whose gallery shows a video. An embedded YouTube or Vimeo player is a third-party load that starts on page view, before anyone clicks, unless a poster stands in for it until the visitor presses play. I changed this in my plugin Reel, which adds a product gallery video to WooCommerce. Since version 1.0.27, YouTube and Vimeo featured videos show a poster with a play button, and the player loads only when the shopper presses it. Visitors who never play load nothing from the player, YouTube plays from youtube-nocookie.com, and the poster fills the reserved 16:9 frame, so nothing moves. With autoplay switched on, the player still embeds directly as before. Version 1.1.0 added the video as the last gallery slide; there, too, the player loads only on click, and autoplay does not apply. To check your own store, open the Network tab in DevTools with consent rejected and reload a product page. If requests to youtube.com, ytimg.com or a Vimeo domain appear before any click, the embed needs a click-to-load poster.

If your Network tab shows requests to Google Analytics after Decline, check which consent mode you run before calling it a leak. Google’s consent mode overview defines two setups:

  • Basic: Google tags are blocked until the visitor interacts with the banner. Google states that no data is sent before consent, “not even the default consent status”.
  • Advanced: tags load immediately with defaults set to denied. While consent is denied, they send the consent state and “measurements without cookies”. With analytics_storage denied, Google Analytics does not read or write its first-party cookies, but measurements are still sent to Google Analytics domains for “basic measurement and modeling purposes”. With ad_storage denied, requests go through a different domain to avoid previously set ad cookies.

Advanced mode therefore produces Google requests after Decline on purpose. Whether that is acceptable for your site is exactly the Article 5(3) question from the EDPB section above, and it is a decision for you and your adviser, not something the tag can settle. Basic mode is the setup where Decline means no Google request.

Two configuration errors from Google’s implementation guide cause real leaks in either mode. The default consent command must run before any config or event command, otherwise tags fire with no consent state set. And if the banner loads asynchronously, Google says to use wait_for_update so tags wait for the banner’s update instead of firing first. Google also recommends scoping defaults to the regions where you show a banner; a region list that misses a country means visitors there get no denied default at all.

#Shopify: Customer Privacy API and app pixels

Shopify’s web pixels documentation says web pixel app extensions honor the Customer Privacy API and, in regions where consent is required, their callbacks run only after consent is given. That is why app pixels behave. Theme code does not get that treatment.

For anything in the theme, use the Customer Privacy API. It exposes analyticsProcessingAllowed(), marketingAllowed() and preferencesProcessingAllowed(), and fires a visitorConsentCollected event when the choice changes. Shopify’s documentation adds that the default depends on the store’s privacy settings: in regions configured to require consent, non-essential purposes are off until consent; elsewhere everything is allowed. Check those settings first, because a region left on the permissive default looks like a broken banner.

The original poster shared a pattern that loads a vendor script only when analyticsProcessingAllowed() returns true, on load and again on visitorConsentCollected. They noted the docs show version 0.2 of the API while their themes had already loaded 0.1 through the banner; that is their observation, and the current documentation shows 0.2.

#WooCommerce, WordPress and headless sites

There is no platform-level sandbox here, so the gate is your job. A consent plugin only blocks scripts it knows about. A thread on r/woocommerce in October 2026 about choosing a consent plugin ended up mostly about testing, and one reply named the two gaps that GTM-based setups miss:

Consent Mode only talks to Google tags. Your Meta pixel doesn’t read those signals. […] Not every pixel lives in GTM. WooCommerce plugins (Meta for WooCommerce, Google for WooCommerce and similar) often add their own pixel straight into the page. GTM Preview won’t show those at all.

(u/Bignose_boy, r/woocommerce, 12 October 2026)

The same commenter’s tip: after Accept, two requests to fbevents.js mean the pixel is loaded twice, once by GTM and once by a plugin. Another reply added a case most first-visit tests skip:

I’d also test a returning visitor who withdraws consent without clearing their browser, and someone who accepts analytics but leaves marketing off.

(u/anamap_alex, r/woocommerce, 7 October 2026)

So check three places in the code: header and footer injection fields in the theme or a snippets plugin, plugins that print their own tags (reviews, chat, marketing, the Meta and Google integrations), and embeds stored in post content. Keep cart and session cookies out of the banner’s optional categories: if rejecting empties the cart, a necessary cookie was filed in the wrong group. On a headless frontend the same applies to the root layout and any analytics component. My GA4 setup guide covers wiring Google tags through consent, and the article on reducing third-party script impact shows how to audit what each script loads.

Update, . The consent tool is itself code that runs on every page, so it needs updates and monitoring like any other plugin. The Wordfence weekly vulnerability report for 12 to 18 October 2026 listed four consent plugins. Complianz up to 7.5.5 had a stored XSS that an unauthenticated visitor could plant through comment content; it runs only once the comment is approved, and version 7.5.6 fixes it. WPConsent up to 1.1.9 had a similar comment-based stored XSS when its Script Blocking feature is on (fixed in 1.2.0). The Record of Consent Extension for Complianz up to 2.5 stored injected script through its uid parameter (fixed in 2.6), and Lightweight Cookie Notice up to 1.19 hardcoded a publicly known W3 Total Cache security key, which matters on sites that also run W3 Total Cache (fixed in 1.20). Put the consent plugin on the same update schedule and vulnerability alerts as the rest of the stack. My WordPress security audit covers that check.

#How to find which script or plugin sent a leaking request

Chrome’s Network panel reference documents four features that turn the basic test into a diagnosis:

  • More filters > 3rd-party requests hides everything that comes from your own origin, so after Decline you see only the requests worth questioning.
  • The domain: filter accepts a wildcard, for example domain:*.google.com, and several properties can be combined with a space. cookie-domain: and cookie-name: show which responses set a given cookie.
  • The Initiator column says whether a request came from the HTML parser, a redirect or a script, and for scripts it links to the exact line in the source file. That line tells you whether the culprit is the theme, a plugin bundle, the tag manager container or an inline snippet.
  • Network request blocking in the drawer lets you block a URL pattern and reload. Block a suspect domain and check whether anything on the page breaks before you remove it for real.

For a record of the test, use Export HAR (sanitized). Chrome’s default HAR export leaves out sensitive data such as cookies, so you can attach it to a ticket or send it to a developer. Run the export once after Decline and once after Accept, and keep both files with the date.

Google’s Tag Manager consent help page describes three features that answer most “why did this tag fire” questions:

  1. Consent Initialization - All Pages trigger. Every web container has it. It fires before all other triggers, including Initialization, and is meant only for tags that set or update consent, such as the banner template. If your banner tag runs on a later trigger, other tags can fire before any consent state exists.
  2. Additional Consent Checks per tag (Advanced Settings > Consent Settings). “Not set” is the default and means no extra check. “Require additional consent for tag to fire” makes the tag fire only if every listed consent type is granted. A tag without built-in consent checks, such as a Meta pixel added as Custom HTML, fires on its trigger whatever the visitor chose, unless this setting holds it back.
  3. Consent Overview. Enable it under Admin > Container Settings. It splits tags into “Consent Not Configured” and “Consent Configured”, and lets you edit several tags at once. Any marketing tag in the first group is a candidate leak.

Then verify the result in Tag Assistant. Google’s consent debugging guide says to select the earliest Consent event in the Summary and check that ad_storage, ad_user_data, ad_personalization and analytics_storage were set, and to compare the On-page Default and On-page Update columns in the Consent tab. The guide names “default consent set too late” as a typical error: the page opens, an ad tag fires, and only then is the default set to denied. It also warns against calling the gtag consent command from a Custom HTML tag. If you use regional defaults, repeat the check with a simulated location in Chrome.

#Next.js, Astro and Cloudflare Zaraz: what the tools do and do not gate

  • Next.js: the @next/third-parties documentation says the GoogleTagManager component fetches the container after hydration, and YouTubeEmbed uses lite-youtube-embed under the hood. Neither checks consent. The lite-youtube-embed readme says the player uses youtube-nocookie.com, and its default poster image is loaded from i.ytimg.com, a Google domain, before anyone clicks. For a strict setup, supply your own poster image.
  • Astro and other static or headless frontends: there is no framework-level consent layer. The script tag in your base layout is the gate, so load vendor scripts from a small client script that checks the stored choice first.
  • Cloudflare Zaraz: the Zaraz consent management docs state that a newly added tool has no purpose assigned and “will skip consent by default”. Assign every tool to a purpose or it loads for everyone. Zaraz caches triggers for tools that wait for consent and fires them if consent is given later, and zaraz.showConsentModal() reopens the modal, which you can bind to a “privacy settings” link.

#Troubleshooting table: symptom, cause, where to look

What you see after DeclineLikely causeWhere to look
Google Analytics requests, no _ga cookieAdvanced consent mode working as designedYour consent mode choice, not the tag
_ga or _gcl cookies appearDefault consent missing or set after the tagsTag Assistant, earliest Consent event
Meta or TikTok pixel firesNon-Google tag with “Not set” consent checks, or a plugin pixel outside GTMGTM Consent Overview, Initiator column
fbevents.js loads twice after AcceptPixel in GTM and in a platform pluginInitiator column on both requests
Leak only on product or blog pagesEmbed or widget in that templateRepeat the test on each template
Leak only for some countriesRegional defaults or Shopify region settings miss a countrySimulated location in Chrome, store privacy settings
Everything fine on first visit, leaks after a withdrawalBanner stores the new choice but does not send an updateConsent update in Tag Assistant after changing the choice

#How to fix each leak

  • Embeds (YouTube, Vimeo, maps): replace the iframe with a click-to-load placeholder, a button or thumbnail that creates the iframe only on click or once consent is given. The thumbnail image must be served from your own server, or it is a third-party request too.
  • Fonts: download the font files, serve them from your domain with @font-face, and remove every link to fonts.googleapis.com.
  • Chat widgets: show a local “Chat with us” button and load the vendor script on click or after consent.
  • Hardcoded pixels: move them into the tag manager or the platform’s pixel system, so the consent check applies.
  • Tag manager: set consent requirements on every tag, run the default command first, and test in preview with consent denied.

Then rerun the devtools test and fill in the same table. Every row in the “after Decline” column should now show either your own domain or a service you have decided to allow on purpose, with the reason written next to it.

#Which banner designs regulators already flagged

The EDPB’s Cookie Banner Taskforce report (adopted 17 January 2023) lists practices that national authorities discussed in coordinated complaints. Most of them can be checked in the same session as the network test:

  • No reject button on the first layer. A vast majority of authorities considered a banner with an accept button but no reject option on the same layer an infringement.
  • Pre-ticked boxes on the second layer. The taskforce confirmed these do not give valid consent.
  • A link instead of a button for rejecting, and button colours or contrast that push “accept all”; the report says contrast needs a case-by-case check rather than a fixed standard.
  • Cookies labelled “essential” that are not strictly necessary. The report notes that site owners are responsible for keeping the cookie list current and demonstrating why each listed cookie is essential.
  • No easy way to withdraw. Authorities expect a permanently visible link or small icon that reopens the privacy settings. On Shopify, privacyBanner.showPreferences() from the Customer Privacy API opens the preferences modal; on Zaraz, zaraz.showConsentModal() does the same.

#Who enforces this in the UK

The UK rule sits in regulation 6 of PECR. The ICO’s cookies guidance says it applies to “any other type of technology” that stores or accesses information on a device, and notes the page is based on the previous guidance while a revised version on storage and access technologies is under consultation. Check the current page before relying on it.

If you want a second pair of eyes, my WooCommerce EU readiness audit includes the consent test on every template, and the free website check flags third-party requests on a public URL.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Explore other WordPress services and knowledge base

Frequently asked questions

Practical answers to apply the topic in real execution.

Does a cookieless request still need consent#
Possibly. The EDPB guidelines treat JavaScript that makes the browser send information as gaining access to the device, and Planet49 says the rule applies whether or not the data is personal. Whether an exemption fits is a legal assessment for your case, but no cookie does not mean out of scope.
Is youtube-nocookie.com enough to stop YouTube tracking#
No. Google describes privacy-enhanced mode as stopping embedded views from personalizing the viewer's YouTube experience. It does not say the embed makes no requests to Google. A click-to-load placeholder that creates the iframe only after a click or consent is what keeps the request from happening.
Why do Shopify app pixels behave but my theme scripts do not#
Web pixel app extensions run in Shopify's sandbox and honor the Customer Privacy API automatically. A script pasted into theme.liquid or a section runs outside that sandbox, so it loads on every page view until you wrap it in a check such as analyticsProcessingAllowed() or marketingAllowed().
Is self-hosting Google Fonts enough to fix the Munich problem#
For fonts, yes: the Munich court's reasoning was that Google Fonts can be used without the browser connecting to a Google server. Check that no plugin, theme or embed still loads fonts.googleapis.com or fonts.gstatic.com after the change.

Related Articles