---
name: consent-debugging
description: Proves whether consent defaults, updates and withdrawals actually control what every tag sends, using outgoing requests, cookies and Tag Assistant as evidence. Covers Google Consent Mode v2 signals, basic vs advanced, region defaults, wait_for_update, url_passthrough and ads_data_redaction, the gcs, gcd and UET asc parameters, GTM consent settings, Cookiebot and OneTrust, late banners, and Meta, TikTok, LinkedIn, UET and product analytics pixels. Produces a region-by-choice test matrix and a pass or fail report. Use when asked to test or verify consent mode v2 or run a consent audit, consent mode looks installed but data drops or leaks, cookies or pixels fire before consent or after reject, Cookiebot, OneTrust or Usercentrics is not blocking, a CMP was installed or changed, GA4 or Google Ads shows consent warnings or ad_user_data is missing, someone asks what gcs=G100 means or how to decode gcd, conversions fell after a banner change, or a cookie banner release needs sign-off.
license: CC-BY-4.0
metadata:
  tier: free
  category: tracking-troubleshooting
  version: "0.1.0"
  last_reviewed: "2026-10-17"
  status: reviewed
---

# Consent debugging

Proves, case by case, that the visitor's consent choice controls what each tag stores and sends. A visible banner, a green CMP dashboard or a "consent mode detected" badge is not evidence. The evidence is the consent state the tags saw, the requests that left the browser and the cookies that were written, captured per region and per choice against a written expected behaviour.

## When to use

Use this skill when:

- Consent mode is installed but GA4 or ad platform data dropped, or data appears for visitors who rejected.
- Tags, pixels or cookies appear before the banner is answered or after a reject.
- A CMP was installed, upgraded, replaced or reconfigured, or a new region rule was added.
- GA4 Consent settings, Google Ads conversion diagnostics or the Microsoft UET dashboard warn about missing consent signals.
- A banner or tag release needs a documented pass or fail before it ships.

Use something else when:

- Nothing is implemented yet, or the question is basic vs advanced, which CMP, or which countries need consent: use the consent-mode-v2-setup skill. Legal questions go to the privacy owner.
- The whole container needs auditing, not just consent: use gtm-container-audit.
- Browser evidence passes but consent is lost between a server container and a destination: use server-side-tracking-debug.
- Consent is verified and the question is why platforms and analytics disagree: use platform-number-reconciliation.
- Sessions break across domains or SPA routes: use cross-domain-and-spa-qa, then return here for the consent cases.
- If a named skill is not installed, say so, record that area as not covered in the write-up, and do not improvise its work.

## Inputs

Read these first if they exist:

- `martech-context/stack.md`: CMP, containers, hardcoded tags, server container, tag gateway, vendor proxy hosts, owners.
- `martech-context/decisions.md`: the approved consent model (mode per region, category mapping, withdrawal rule).
- `martech-context/tracking-plan/`: the consent category on each event contract.

Then gather:

- The approved consent specification. For each region: the default for each Google signal, the banner type, basic or advanced for Google tags, which vendor sits in which CMP category, what dismissing the banner means, whether implied consent or Global Privacy Control applies, what happens to events before a choice, and what withdrawal must stop. A test without an expected result cannot fail.
  - If none exists, draft one with the user from the CMP and GTM configuration. Label it "derived from current configuration, not approved" and mark every value the privacy owner has not confirmed as unconfirmed. A draft built from the configuration turns today's bugs into expected results, so no verdict goes above Incomplete until the owner approves it.
- Journeys to test: a landing page, a lead form, checkout or signup, one SPA route change, one cross-domain hop.
- Conversion gate. Conversion cases (C9, C12) and a test click ID on the landing URL write to live systems (CRM records, orders, conversions that Ads and Meta bid on, GA4 sessions labelled paid). Run them only after the site owner approves in writing; record where they run, the test record markers, the exclusion from Ads and Meta and the cleanup owner, and use test payment methods only. Without approval, mark them "not run" with that reason. Details: [references/test-matrix.md](references/test-matrix.md#conversion-and-click-id-gate).
- Regions in scope and how you will appear to be in each (VPN or proxy egress).
- Read access: a GTM container export or read-only API access, CMP admin read access or the CMP's public configuration file.

Never ask for, print or store credentials or customer personal data. HAR files and cookie dumps hold identifiers: store them with the project, redact before sharing, and record cookie names rather than values.

## Procedure

### How the agent runs this

- Mode A, the agent has its own browser (agent-owned or Playwright, never the user's everyday browser): a fresh context per run, region from a proxy or VPN egress only (geolocation emulation does not change the IP), HAR and all cookies including HttpOnly captured through the tool, real banner buttons clicked for sign-off.
- Mode B, no browser tool: give the user the steps per case, then analyse the HAR, snapshot, cookie list (HttpOnly column) and Tag Assistant Consent tab they return.
- Either way, read every HAR through [scripts/har-consent-extract.mjs](scripts/har-consent-extract.mjs), which prints the consent timeline in order. Details: [references/test-matrix.md](references/test-matrix.md#how-the-agent-runs-each-case).

For quick triage, run the five-minute first check in [references/test-matrix.md](references/test-matrix.md#five-minute-first-check-triage-not-sign-off). It finds obvious faults; it is never a sign-off.

### 1. Write the expected behaviour before testing

Copy [assets/consent-test-matrix.csv](assets/consent-test-matrix.csv) and fill one row per region and choice: each Google signal, Google behaviour, UET `asc`, `url_passthrough` and redaction, each non-Google vendor, cookies allowed, and whether the essential journey completes. Cases C0 to C14, region list, minimum set and run rules: [references/test-matrix.md](references/test-matrix.md).

- If the specification does not say basic or advanced for Google tags, stop. Basic sends nothing to Google for a denied visitor; advanced sends cookieless denied-state requests. The same request passes in one and fails in the other.

### 2. Inventory every consent writer and every sender

- Writers of the Google default: the CMP template in GTM, a hardcoded `gtag('consent', 'default', ...)`, a CMS plugin, a second container, the Google tag settings "Set consent mode override" and "Manage default consent settings", and TCF when the CMP runs it.
- Senders: GTM tags, hardcoded pixels in the theme or app, embeds and iframes, Google tag gateway or CDN injection, server-side containers, vendor proxy hosts.
- Find them from the page source, network initiator chains, the GTM export and the CMP's published configuration.
- Decision rule: more than one writer of the Google default is a finding even when the values agree today.

### 3. Prepare a clean rig

- One fresh profile per case, no stored consent, no ad blocker. Network log with "Preserve log" and cache disabled; a HAR per case.
- Cookie evidence comes from DevTools Application > Cookies (HttpOnly column) or the HAR's Set-Cookie headers. `document.cookie` and the snapshot cannot see HttpOnly cookies such as a server-set `FPID`.
- Confirm the CMP sees the intended region (for Cookiebot, `Cookiebot.regulations.gdprApplies`; a `CookieConsent` value of `-1` means no consent is required and everything is accepted).
- Connect Tag Assistant. Run [scripts/consent-snapshot.js](scripts/consent-snapshot.js) at each checkpoint.

### 4. Test the default (no interaction)

- Tag Assistant: the earliest Consent event lists all four signals with the expected values and comes before the first tag. An empty Consent tab means consent mode is not implemented. "Could not connect" means the container or Google tag is blocked, which is expected in basic mode before a choice.
- Basic: no Google measurement requests and no `_ga`, `_ga_*`, `FPID` or `_gcl_*` cookies.
- Advanced: Google requests carry denied state in `gcs` and `gcd`, and none of those cookies are written.
- Non-Google vendors: no event requests or cookies unless the vendor holds data under an approved consent API. A library load alone is not an event.
- Microsoft UET: `asc=D` or no UET events. Denied beacons go to `bat.bing.net`, so filter on both UET hosts.
- Run race-sensitive cases three times, one with network throttling. A case passes only on three of three.

### 5. Test each choice (update)

Reject all, accept all, each partial choice, closing the banner (C10) and browsing without a choice (C11).

- The latest Consent event shows the update, and the next requests carry the new state (UET `asc=G` after a grant).
- A tag blocked at page load fires exactly once after acceptance on the same page. The first page view is neither lost nor duplicated.
- No business event (purchase, lead) fires because consent changed.
- After every reject, the essential journey (form, checkout, login) still completes.
- Events held before the choice: TikTok's `holdConsent` releases its queue on `grantConsent`, and Amplitude sends events tracked before `init`. Compare with the specification; C12 tests a conversion before any choice.

### 6. Test returning visitors and navigation

- Reload, then navigate. The stored choice must be in place before the first tag.
- Decide on the outcome: after a stored grant, the first Google request's `gcd` letters show `r` or `v`, not `p`, and Tag Assistant shows the update before the first tag.
- On a failure, find why the stored choice is not applied at Consent Initialization before anyone proposes a longer `wait_for_update`. Google does not document when that wait starts; report any timing as approximate, from the consent default.

### 7. Test withdrawal

- Reopen preferences (`Cookiebot.renew()`, the OneTrust preference centre) and withdraw.
- An update to denied is sent on the same page, later requests carry denied state, and non-Google vendors stop (their revoke calls run, or they no longer load).
- Meta: no automatic events after withdrawal on the same page. A loaded pixel keeps sending unless `fbq('consent', 'revoke')` runs; GTM blocking alone does not stop it.
- Record whether existing cookies are deleted or left, and confirm none is refreshed. Whether they must be deleted is a specification decision.

### 8. Test region branches

- Every region default and the unknown-location fallback. Codes are ISO 3166-1 alpha-2 or ISO 3166-2 (`GB`, not `UK`; `US-CA`). The more specific region wins.
- The CMP's geolocation rule (which banner shows) and the Google region defaults are separate settings that must agree.
- A CMP location override proves the banner rule only. Google region defaults are proven only from a matching IP egress. Record the CMP region flag and the first request's `gcd` default letter for every region case.
- Global Privacy Control (C13) in opt-out regions; CMP blocked (C14) in the main opt-in region.

### 9. Test url_passthrough and ads_data_redaction

Run in C0, C1 and C2 with the approved test click ID. Full evidence rules: [references/evidence-and-signals.md](references/evidence-and-signals.md#url_passthrough-and-ads_data_redaction).

- `url_passthrough`: same-domain links gain `_gl` or the click parameter, and the next page's Ads and GA4 requests carry the click while no `_gcl_*` cookie exists. Fail when it is lost on a redirect, a POST form, or a canonical or cache rule.
- `ads_data_redaction`: only while `ad_storage` is denied, only on Ads and Floodlight requests. Pass when the click ID appears in no click-identifier parameter there.

### 10. Read the lagging destination signals

GA4 Admin consent settings, Google Ads conversion diagnostics, the UET dashboard and the GA4 BigQuery `privacy_info` fields confirm the pattern days later. They never replace browser evidence. Paths, lags and a per-country query: [references/evidence-and-signals.md](references/evidence-and-signals.md).

### 11. Write up pass or fail

Use [references/pass-fail-writeup.md](references/pass-fail-writeup.md). Grade every failure:

- S1: data sent or storage written beyond what the visitor's current choice allows. An essential journey that breaks after a reject or withdrawal is graded like S1, owned by the site owner.
- S2: permitted data lost or duplicated.
- S3: configuration risk with no wrong behaviour observed yet.
- Verdict: Fail with any open S1. Otherwise Incomplete while any minimum-set case is not run or the specification is unapproved. Conditional pass with S2 only when each has an owner, a date and the user accepts it. Pass with S3 only or none.
- Specification gaps (an undefined region, no rule for GPC or implied consent) go in their own list for the privacy owner. They are not test failures.

Depth for each layer: [references/gtm-consent-settings.md](references/gtm-consent-settings.md), [references/cmp-integration-traps.md](references/cmp-integration-traps.md), [references/non-google-tags.md](references/non-google-tags.md).

## Common failure modes

- Default after the first tag. Detect: Tag Assistant flags it; a Google request precedes the first Consent event; it varies with throttling.
- Update without default. Detect: no default in the earliest Consent event; `gcd` letters `m` or `n`.
- Two writers. Detect: two `consent default` entries, values that change between page loads, or a TC string that disagrees with `gcs`.
- Wrong mode delivered. Detect: Google tags with "Require additional consent" or exception triggers when advanced was specified; denied-state Google requests before a choice when basic was specified.
- Missing v2 signals. Detect: `ad_user_data` or `ad_personalization` absent from the default; GA4 consent settings alert.
- First page lost after accept. Detect: blocked tag on All Pages with no consent-update trigger.
- Duplicate page view. Detect: one tag on both All Pages and the CMP's update event.
- Banner late. Detect: CMP script async, deferred or reordered, or Google tags injected by a CDN.
- Identity outside cookies. Detect: GA4 `cid` stable across pages with analytics storage denied and no `_ga`; an HttpOnly `FPID`.
- Pre-consent events released on grant. Detect: requests after accept carry events from before it.
- Implied consent. Detect: a grant recorded after scrolling or closing the banner.
- Withdrawal ignored by non-Google tags. Detect: Meta automatic events or other vendor requests after withdrawal on the same page.
- Region defaults wrong. Detect: `UK` for `GB`, no unknown fallback, CMP and Google regions disagree.

## Worked example

Acme Outdoor (fictional, `example.com`, GTM-XXXXXXX) runs Cookiebot through GTM with advanced mode in the EEA and GB. Twenty-one of twenty-eight planned cases ran; the unknown-location cases had no usable egress. Findings: a theme-level `gtag('consent','default')` without the v2 signals competed with the template (S3); the Meta pixel required `ad_storage` but stayed on All Pages, so it never fired on the accept page (S2); a returning visitor's first GA4 request went out denied in the throttled run because the stored choice was not applied at Consent Initialization (S2); TikTok released pre-consent events on grant, including a lead submitted before any choice (S1). Verdict: Fail, coverage incomplete. Full write-up: [references/pass-fail-writeup.md](references/pass-fail-writeup.md#worked-example).

## Acceptance checks

- Specification: approved by the privacy owner, with an expected value for each Google signal and each vendor in every row. Pass: no blank or unconfirmed expectations; otherwise the verdict stops at Incomplete.
- Inventory: every consent writer and sender is listed with its owner. Pass: each network request in the default case maps to a listed sender.
- Default: earliest Consent event before the first tag in three of three runs per region. Pass: no late-default error in Tag Assistant and no earlier Google request in the extractor timeline.
- Choices: for reject, accept, each partial choice and dismissal, requests and cookies after the choice match the row. Pass: zero S1, each blocked tag fires once, and the essential journey completes after every reject.
- Returning and withdrawal: after a stored grant the first request's `gcd` letters show `r` or `v`; after withdrawal no gated vendor sends, Meta automatic events included. Pass: request parameters and HAR prove it.
- Regions: each region branch and the unknown fallback ran from the matching IP egress, with the CMP region flag and the `gcd` default letter recorded. Pass: no region marked pass on a CMP location override alone.
- Optional settings: `url_passthrough` and `ads_data_redaction` match the specification in C0, C1 and C2. Pass: `observed_gl` and `observed_click_id_in_ads` filled and matching.
- Coverage: every minimum-set case ran or is marked not run with a reason. Pass: the verdict is no higher than Incomplete while any is not run.
- Report: each case cites the HAR and extractor output, Tag Assistant evidence and the snapshot, with run mode, container and CMP configuration versions, browser, date and gate approval. Pass: a reviewer can repeat any case from the record.

## Boundaries

- Read-only. This skill does not edit GTM, the CMP, site code or vendor settings; conversion cases run only under the written approval in Inputs. Fixes are a change list for the owner and stop for human approval. Use gtm-change-workflow for the change; if it is not installed, deliver the change list as a Markdown file and stop.
- It does not decide what is legally required. Defaults, categories and withdrawal rules come from the privacy owner.
- Hand-offs and their safe modes:
  - Google Tag Manager has no official Google MCP server. Prefer a container export or the Tag Manager API with the `tagmanager.readonly` scope. Stape's GTM MCP server mixes reads and writes (including publish and delete) in every tool behind an `action` parameter, so no tool allowlist makes it read-only; if used, call only the actions `get`, `list`, `live`, `latest` and `getStatus`.
  - Google's GA4 MCP server (experimental, read-only scope): its realtime report confirms events arrive after a grant. Read consent state from browser evidence and BigQuery, not reports.
  - Meta's Ads MCP server: call only its read tools. Never call `ads_pixel_event_create`, `ads_pixel_event_update`, `ads_pixel_event_delete`, `ads_pixel_parameter_create`, `ads_pixel_parameter_update` or `ads_pixel_parameter_delete`.
  - Meta's browser extension (now Meta Ads Data Advisor) can apply fixes, so use it to read only. Test Events and TikTok's Pixel Helper are read-only evidence.
  - OneTrust's developer MCP serves API specs for search and reading; its `execute-request` tool can call live APIs, so it needs approval first.
  - Cookiebot help pages block non-browser clients. Open them in a browser.
  - Microsoft UET consent is checked with the UET Tag Helper extension (it shows `asc` as words; the request carries `G` or `D`) and the UET dashboard.

## Live docs

Platform settings, limits and menu paths change. Before stating any parameter name, limit, quota or UI path:

- Fetch the current page from the vendor docs MCP server or llms.txt listed in `sources.yaml`, or open the canonical URL.
- If the live page disagrees with this skill, follow the live page and tell the user what changed.
- Cite the URL you used.

The entries that matter most: Google's consent mode overview, consent setup and Tag Assistant debugging guides, Cookiebot's GTM and auto-blocking articles, OneTrust's Google Consent Mode article, Microsoft's UET consent page and `bat.js`, and the Meta and TikTok consent pages. Facts were checked on 2026-10-17. Google does not document the `gcs` and `gcd` encoding; the decoding here is community knowledge, so confirm it in Tag Assistant.
