# Evidence and signals

What each Google consent signal changes on outgoing requests, how to read the consent state there, what Tag Assistant shows, which cookies to watch, how to prove `url_passthrough` and `ads_data_redaction`, and where the lagging destination signals live. Every fact here was checked against the live vendor page on 2026-10-17; the URLs are in `sources.yaml`.

## What each Google signal changes on the wire

Definitions live in Google's consent mode overview and the GA4 consent mode reference; read them there. Test for these request-level effects, not for the label.

- `ad_storage` denied: no new `_gcl_*` or `FPGCL*` cookies; Ads and Floodlight requests move to a cookieless domain such as `pagead2.googlesyndication.com`. The full page URL, including ad-click parameters, is still sent.
- `analytics_storage` denied: no `_ga`, `_ga_*`, `FPID` or `FPLC` cookie is written or refreshed.
  - Practitioner check: with `url_passthrough` off, the GA4 `cid` value must differ between page 1 and page 2 of the same visit. Google lists a random number per page load among what cookieless pings carry.
  - A `cid` that stays the same across pages while no `_ga` cookie exists means identity is stored somewhere else: an HttpOnly `FPID` set by a server container, localStorage, or a custom client ID. That is S1 in an opt-in region.
  - With `url_passthrough` on, compare the `cid` with the parameter on the link before concluding.
- `ad_user_data` denied: no `user_id` and no enhanced conversion data on Ads requests. Search the HAR for the hashed fields your enhanced conversions setup sends.
- `ad_personalization` denied: verify at run time which Ads request parameter marks the request as non-personalised, and that it appears in every case where the signal is denied. Server containers list `x-ga-npa` among Google's internal parameters.
- A type that is never set counts as not granted.
- Google signals is no longer a consent switch: since 15 June 2026 Consent Mode in Google Ads controls Google Ads cookies and IDs. Do not test the Google signals toggle as a consent control.

## Basic and advanced, as evidence

- Basic, before a choice: zero Google measurement requests and no Google cookies. Tag Assistant may say "Could not connect" because the Google tag is blocked; in basic mode that is expected.
- Basic, after a grant: the default and then the update arrive with the first requests.
- Advanced, before a choice or after a reject: Google requests with denied state in `gcs` and `gcd`, and no Google cookies written.
- Modelling eligibility is not an implementation test. Its thresholds belong to the consent-mode-v2-setup skill; if that skill is not installed, link Google's GA4 behavioural modelling page and do not grade it here.

## Consent parameters on outgoing requests

What Google documents (consent mode overview, "Consent mode HTTP parameters"):

- Consent is translated into HTTP request parameters such as `dma`, `gcd` and `gcs`; consent to Google services is encoded in `dma_cps`.
- `gcs` transmits `ad_storage` and `analytics_storage`.
- `gcd` is sent to Google services whether or not consent mode is active, and encodes the detailed consent choice through the consent types.
- These fields "may be subject to change as these services evolve".

What Google does not document: the value encoding of either parameter. The decoding below is published by CMP vendors (see the community decoding entry in `sources.yaml`), not by Google. Use it to triage, and confirm against Tag Assistant before you report a value.

- `gcs` (community decoding): `G1`, then a digit for `ad_storage`, then a digit for `analytics_storage`; 1 is granted and 0 is denied.
  - `G100`: both denied.
  - `G110`: ad storage granted, analytics denied.
  - `G101`: ad storage denied, analytics granted.
  - `G111`: both granted.
- `gcd` (community decoding): one letter per signal in the order `ad_storage`, `analytics_storage`, `ad_user_data`, `ad_personalization`, separated by digits that do not change the reading. Each letter combines the default with the update:
  - `l`: no default, no update (consent mode not seen for that signal)
  - `p`: default denied, no update yet
  - `q`: default denied, update denied
  - `t`: default granted, no update yet
  - `r`: default denied, update granted
  - `m`: no default, update denied
  - `n`: no default, update granted
  - `u`: default granted, update denied
  - `v`: default granted, update granted
- Triage rules from the decoding:
  - `m` or `n` on any signal: an update arrived with no default. Find the missing default.
  - `l` on the last two signals while the first two carry letters: a v1-era setup without `ad_user_data` and `ad_personalization`.
  - `t` or `v` in a region whose specification says denied by default: the wrong region default, or another writer.
- Where to look: the query string of GA4 requests (path `/g/collect`) and of Google Ads and Floodlight requests. With Google tag gateway or a server container the host is your own domain, so filter the Network panel on `gcs=` or `/g/collect`, not on a Google hostname.
- Server containers: Google lists `x-ga-gcs`, `x-ga-gcd`, `x-ga-gcu`, `x-ga-dma`, `x-ga-dma_cps` and `x-ga-npa` as internal parameters and warns that changing them may break Google tags. Check they arrive in server preview; diagnosing their loss belongs to server-side-tracking-debug.
- Microsoft UET carries the state in `asc` on each event request:
  - The query string holds a single letter: `asc=D` (denied) or `asc=G` (granted). UET Tag Helper shows the same values as the words denied and granted, so search the HAR for `asc=D` or `asc=G`, not for the words.
  - Consent updates that UET picks up from Google's consent state (through GTM) are also sent as a separate `gtmConsent` beacon carrying `gasc=G` or `gasc=D`.
  - Denied and before-load UET beacons go to `bat.bing.net`, not `bat.bing.com`. Filter on both.
  - Before consent or after a reject: `asc=D` or no UET events. After a grant: `asc=G` on every later event.
  - This encoding is read from Microsoft's `bat.js` (checked 2026-10-17). Confirm it against Microsoft's "Setting up UET for consent mode" page at run time and cite that page.

## Tag Assistant consent view

From Google's "Troubleshoot consent mode with Tag Assistant" guide:

- Start a session at tagassistant.google.com with the page URL.
- Default: in the Summary, select the earliest Consent event and read the API Call section for the four signals. Or open a tag's Output, select the Consent tab and read the "On-page Default" column.
- Update: select the most recent Consent event, or read the "On-page Update" column.
- Per tag: in the Summary, open the Tags tab and select a tag to see whether it behaved according to consent.
- An empty Consent tab means consent mode is not implemented on the page.
- "Could not connect" means the container or the Google tag is blocked from loading. In basic mode, before a choice, that is expected.
- A default set too late (after a tag read or wrote a cookie) is reported as an error. Google shows the message only as a screenshot, so record the wording you see.
- Repeat for each simulated location when region defaults exist.

## Console evidence

- `dataLayer`: hardcoded `gtag('consent', ...)` calls appear as entries. A CMP template that uses `setDefaultConsentState` registers the state inside GTM instead; Cookiebot states its template defaults are not pushed to the dataLayer. An empty dataLayer search is therefore not a failure.
- `google_tag_data.ics.entries`: an internal, undocumented Google object holding the default and update per consent type. Cookiebot's own verification script reads it. Use it only as a supplement; it can change without notice.
- CMP state: `Cookiebot.consent.statistics` and the other category flags, `Cookiebot.consent.method` (`implied` or `explicit`), `Cookiebot.hasResponse`; for OneTrust, `OnetrustActiveGroups` and the `groups` field of the `OptanonConsent` cookie.
- [scripts/consent-snapshot.js](../scripts/consent-snapshot.js) prints all of the above plus the `gcs`, `gcd` and raw `asc` values found in Resource Timing, and per vendor the event requests and library loads counted separately. Resource Timing can miss requests and `document.cookie` cannot see HttpOnly cookies; the HAR is the record.
- [scripts/har-consent-extract.mjs](../scripts/har-consent-extract.mjs) reads a saved HAR and prints the consent timeline: start time, host, path, vendor, event names, `gcs`, `gcd` decoded per signal, `asc`, and Set-Cookie names with the HttpOnly flag. Run it on every case HAR before writing results.

## Cookies to watch

Record names, creation time, expiry and the HttpOnly flag, not values.

- Where to read them: DevTools Application > Cookies (it has an HttpOnly column), or the Set-Cookie response headers in the HAR. `document.cookie` and the snapshot script cannot see HttpOnly cookies, so they are a supplement, never the evidence for a cookie pass.
- Google Analytics (Google's advertising cookies page, partner-domain list): `_ga` and `_ga_<container-id>` (two years), `FPID` (two years, set by a server container and often HttpOnly) and `FPLC` (20 hours).
- Google Ads on the advertiser domain: `_gcl_au`, `_gcl_aw`, `_gcl_dc`, `_gcl_gb`, `_gcl_ag`, `_gcl_gs`, `FPAU`, `FPGCLAW`, `FPGCLGB`, `FPGCLGS`, `FPGSID`.
- Meta: `_fbp` (set by the pixel with first-party cookies) and `_fbc` (holds the `fbclid` click ID).
- Every other vendor: take the current names from the CMP scan report rather than from memory.
- The CMP's own cookies (`CookieConsent`; `OptanonConsent` and `OptanonAlertBoxClosed`) are expected in every case.
- Pass rules:
  - In an opt-in region before a choice: none of the vendor cookies above, and no Set-Cookie from a first-party measurement endpoint (Google tag gateway path, server container or vendor proxy host).
  - After withdrawal: no vendor cookie has a later expiry than before withdrawal. Whether existing cookies must be deleted is a specification decision.

## url_passthrough and ads_data_redaction

Both are set in the consent setup and change requests only while storage is denied. Run these checks in C0 or C1 with the approved test click ID on the landing URL (see the conversion gate in [test-matrix.md](test-matrix.md#conversion-and-click-id-gate)).

- `url_passthrough` (Google's conditions: a consent-aware Google tag on the page, consent mode implemented, a same-domain link, and for Ads and Floodlight a GCLID or DCLID in the URL):
  - Expected: after the page has loaded, links to pages on the same domain gain `_gl` or the click parameter (`gclid`, `dclid`, `gclsrc`, `wbraid`).
  - Expected: the next page's Ads and GA4 requests carry the click information while no `_gcl_*` cookie exists.
  - Fail when the parameter is lost on a redirect (Google requires redirects to pass the query parameters), on a POST form, or through a canonical, cache or router rule.
  - Pass condition: the matrix's `expected_url_passthrough` matches what `observed_gl` shows in C0, C1 and C2.
- `ads_data_redaction` (Google: active only while `ad_storage` is denied, and only on Google Ads and Floodlight requests):
  - Pass: the GCLID or DCLID value appears in no click-identifier parameter on any Ads or Floodlight consent or conversion request while `ad_storage` is denied.
  - Google says the full page URL, including ad-click parameters, is still sent while `ad_storage` is denied, and does not document whether redaction removes the click ID from inside the page URL parameter. Record what you see in `observed_click_id_in_ads`; it is a fail only if the specification says so.
  - After a grant no redaction is expected.
- Acceptance: both settings match the specification in C0, C1 and C2.

## Destination signals (lagging, confirmatory)

- GA4: Admin, "Data collection and modification", "Consent settings".
  - Shows the share of traffic and conversions from the EEA.
  - Per data stream: "Advertising related consent signals" and "Behavior analytics consent signals".
  - Ads measurement consent is read from `ad_user_data`, ads personalisation from `ad_personalization`.
  - Notifications can take 48 to 72 hours to update after a fix.
- Google Ads: Goals, Conversions, Summary, open a website conversion action, Diagnostics tab.
  - Statuses: "Consent mode is implemented" and "Consent mode is implemented and modeling is active".
  - The active status can take 48 hours, and up to 2 weeks in some cases.
  - Whether modelling becomes active depends on traffic thresholds, not on the implementation; do not grade it here.
- Microsoft UET: Conversions, UET tag, Consent Signal column.
  - Healthy: more than 75% of events in the last 7 days carry a valid signal.
  - Moderate: above 0% and up to 75%.
  - Missing: 0%.
  - The "Test your tag" tab shows Present or Not present per event.
- GA4 BigQuery export: `privacy_info.analytics_storage`, `privacy_info.ads_storage` and `privacy_info.uses_transient_token`, each `Yes`, `No` or `Unset`. Google defines `uses_transient_token` narrowly: Analytics storage was denied and the developer enabled measurement without cookies based on transient tokens in server data. Do not read it as "all cookieless traffic".

```sql
-- Daily event share by consent state and country (fictional project and dataset)
SELECT
  event_date,
  geo.country AS country,
  privacy_info.analytics_storage AS analytics_storage,
  privacy_info.ads_storage AS ads_storage,
  privacy_info.uses_transient_token AS uses_transient_token,
  COUNT(*) AS events
FROM `example-project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20261001' AND '20261016'
GROUP BY 1, 2, 3, 4, 5
ORDER BY 1, 2, 6 DESC;
```

- Apply the rules per country, using the specification's opt-in country list. `geo.country` is derived from IP, so it can disagree with the CMP's region for VPN or proxy traffic.
- A jump in `Unset` from a release date: consent state stopped reaching the tags.
- Zero `No` rows for an opt-in country under advanced mode: cookieless pings are not arriving, or the tags actually behave as basic.

## Measuring the real consent rate

- A consent-gated tool cannot measure its own consent rate: in basic mode every rejecter is missing from the denominator, and non-Google tools have no cookieless equivalent.
- Use the CMP's own consent log or reporting, server or CDN logs of page loads, or a first-party beacon that is not consent-gated, carries only the CMP's consent state and no identifier, and is approved by the privacy owner.
- Compare by region and by day around each release. A drop in acceptance right after a banner change points at the banner or its load time, not at the tags.
