How to Honor the GPC Signal, Broken Down by Stack

A browser that sends Global Privacy Control has already made its decision. The visitor is not asking to be shown a banner. Under California's opt-out preference signal rules, that browser is telling your site to stop selling and sharing the personal information it collects during the visit, and your site is expected to act on it without being asked twice.

Reading the signal is the easy half. A header arrives, a JavaScript property is set, and detecting either one takes about a line of code. The half that decides whether you pass is what has to happen next: every tag, pixel, embed, and server-side forwarding call that amounts to a sale or a share has to stop for that request, in whichever system happens to control it. Below are the three obligations, then the three places the suppression actually lands.

Global Privacy Control (GPC) architectural signal flow: Sec-GPC: 1 request header triggering automated firewall interception of third-party tracker beacons
Figure 1: Global Privacy Control (GPC) architectural flow — the Sec-GPC request header acts as an automated gatekeeper to suppress unauthorized tracker beacons.

What honoring the signal actually means

The obligation is not one rule. Three parts of the CCPA regulations combine into what a compliant page load looks like:

  • Treat the signal as a valid opt-out (§ 7025). A qualifying opt-out preference signal must be processed as a request to opt out of sale and sharing for that browser or device, and for any consumer profile associated with it, including pseudonymous ones. The visitor does not have to identify themselves for it to count.
  • Say that you processed it (§ 7025(c)(6)). When the signal is active, the site has to display whether it honored it. Trackers correctly suppressed plus silence about it is still an unmet requirement.
  • Do it without an interstitial, on the frictionless path (§ 7025(f)(3)). Frictionless processing is optional, and it is what lets a business drop the posted opt-out link. In exchange, the site may not respond to the signal with a notification, pop-up, text, graphic, animation, sound, video, or interstitial. Two things stay permitted: showing opt-out status, and linking to a privacy settings page.

California's first public CCPA settlement, the $1.2 million resolution with Sephora in August 2022, rested in part on a failure to honor Global Privacy Control opt-out signals and a failure to cure inside the 30-day window the statute allowed at the time.

CCPA Enforcement Lesson: Sephora ($1.2M Settlement)
California's landmark settlement hinged specifically on the failure to honor Global Privacy Control signals. Trackers configured inside tag managers continued firing because suppression was not treated as a gating dependency before tag execution.

Step 1: Read the signal in both places

GPC travels on two channels at once, and they reach different parts of a stack. The request header Sec-GPC: 1 rides along with every HTTP request the browser makes, so anything running at the origin, in middleware, or at the edge can see it before a byte of HTML is assembled. The property navigator.globalPrivacyControl is a boolean readable by any script on the page, which is how a client-side tag loader learns about the signal without asking your server anything.

Read both. A server-rendered decision covers only what the server renders, and an inline loader that never round-trips to your origin has no way to know a header existed. For the wire-level detail we have written up the full anatomy of the Sec-GPC header separately, including what a correct response looks like.

Dual-channel Global Privacy Control signal detection architecture showing Sec-GPC HTTP request header entering edge middleware and navigator.globalPrivacyControl DOM property evaluated by inline tag loaders
Figure 2: Dual-channel GPC detection: HTTP wire request (Sec-GPC: 1) handled in server edge middleware vs client-side DOM execution (navigator.globalPrivacyControl) by inline tag loaders.

Step 2: Decide what counts as sale or sharing

Suppression is only as good as the category map behind it, and that map is a business decision before it is an engineering one. The question for each tag on your site is whether it hands personal information to a third party for money or other value, or for cross-context behavioral advertising. Three groups, in descending order of how obvious they are:

  • Advertising and marketing tags. Retargeting pixels, conversion tags, ad network beacons, audience-building integrations, and server-side conversion APIs that forward identifiers to an ad platform. This is the clear case, and it is what an outside test looks at first.
  • Social and embedded widgets. Share buttons, video embeds, review badges, chat widgets, and maps often load tracking code alongside the feature you wanted. The feature and the tracking arrive as one bundle, so the decision has to be made about the bundle.
  • Analytics. This one needs a written position rather than a default. First-party measurement with no identifier leaving for advertising purposes is a different posture from analytics configured to build advertising audiences. Whichever you conclude, record the reasoning, the configuration it depends on, and who revisits it when that configuration changes.

Do the mapping once, in a document, and let each implementation below inherit it. Deciding vendor by vendor inside a tag manager UI is how two engineers reach two different answers for one vendor.

Three-tier GPC signal enforcement architecture showing Edge Middleware, Google Tag Manager container gating, and Client CMP script loading layers
Figure 3: Multi-tier enforcement architecture: Coordinating suppression across Edge Middleware (caching & Vary header), Tag Manager (GTM gating variables), and Client CMP (script blocking).

Implementing it in Google Tag Manager

In a tag manager, honoring GPC is a gating problem. The signal has to be resolved into a value the container can branch on, and the affected tags must not fire while that resolution is pending.

GTM Custom JavaScript Variable: {{JS - GPC Active}}·javascript
function() {
  return navigator.globalPrivacyControl === true;
}
  1. Surface the signal as a variable. A custom JavaScript variable returning navigator.globalPrivacyControl gives the container something to read. If your origin already reads the header, pushing that decision into the data layer before the container loads is sturdier still.
  2. Gate the category, not the individual tag. Attach the condition to advertising and marketing tags as a group. Per-tag conditions drift as tags get added; a group rule is inherited by the next tag someone drops into that group.
  3. Block triggers until the signal resolves. The common failure here is a race rather than a wrong rule. Tags bound to container load or initial page view can go out before the consent state exists. Hold those triggers until the state is known, then release them.

One boundary is worth stating plainly: a container enforces the tags inside it. A pixel pasted straight into a theme template, or a vendor snippet added in a landing page builder, never passes through the container, so no container rule reaches it. Those tags have to move into the container or be handled at one of the other two layers.

Implementing it server-side or at the edge

Reading the header at the origin, in framework middleware, or in an edge function gives you the earliest decision point available, before the markup that would have loaded a third-party script is assembled.

  1. Read the header and set a per-request flag. One boolean derived from Sec-GPC, attached to the request context and available to every template and handler downstream.
  2. Render the page without the affected embeds. Not hidden, not loaded and then neutralized. If the script tag never reaches the browser, there is no timing question left to lose.
  3. Apply the same flag to your own outbound calls. Server-side tagging and conversion APIs run entirely outside the browser, so no client-side consent tool touches them. If the request carried the signal, the forwarding call for that visit has to be suppressed in your code.
middleware.ts (Next.js / Edge Worker)·typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const isGpcOn = request.headers.get('sec-gpc') === '1';
  const response = NextResponse.next();
  // Prevent CDN cache poisoning between GPC-on and GPC-off visitors
  response.headers.set('Vary', 'Sec-GPC');
  response.headers.set('x-gpc-status', isGpcOn ? 'honored' : 'inactive');
  return response;
}

The trap at this layer is caching. A CDN or full-page cache keyed on URL alone will serve a response generated for a GPC-off request to a GPC-on visitor, and the origin never sees that second request. Vary the cache key on the header, or move the decision into an edge function that runs on every request and assembles the third-party portion after the cache lookup.

Implementing it through a consent platform

Consent platforms differ in their interfaces, but the configuration has the same three parts everywhere, and each is worth checking on its own:

  • Signal source. Which input the platform treats as an opt-out preference signal, and whether it reads the header, the JavaScript property, or both. Where the platform offers a choice between handling the signal everywhere and handling it for a geographic subset, choose deliberately: how a visitor's location resolves then decides who is covered.
  • Category mapping. Which categories the signal switches off. This is where the document from step 2 gets entered into a system. A vendor sitting in a category the signal does not switch off keeps firing, and the platform is doing exactly what it was configured to do.
  • Enforcement point. How the decision gets applied: script blocking by the platform's own loader, a consent-mode integration with your tag manager, or an API your code calls. Each one covers a different set of scripts.

That last item is the coverage boundary, and it deserves precision because it is not a property of any particular product. A consent platform enforces decisions for the scripts that route through it. Anything loaded by a path that bypasses both the platform's loader and your container sits outside the scope of the configuration, no matter how carefully the configuration is written. Coverage is a property of the site's markup as much as of the platform.

Coverage also changes without anyone editing the consent settings. A theme update, a campaign page built outside the usual template, a vendor snippet pasted into a page builder: each adds a script the platform was never pointed at. A setup that was complete at launch can be incomplete a quarter later while every dashboard still reads green.

Step 3: Show the status without an interstitial

§ 7025(c)(6) requires the site to display whether it processed the signal. § 7025(f)(3) governs how, for a business on the frictionless path: no notification, pop-up, text, graphic, animation, sound, video, or interstitial in response to the signal, with showing opt-out status and linking to a privacy settings page as the two carve-outs.

Read together, they describe something quiet and persistent rather than something that announces itself. A footer line reflecting opt-out state, or a toggle on a privacy choices page that renders as already opted out, satisfies the display duty. What the frictionless path rules out is a modal, a slide-in, or a banner that appears because the signal arrived.

Whichever surface you pick, it has to reflect the real state rather than a fixed string. A standing sentence in a privacy policy is a claim about policy. The requirement is a display of what happened on this request.

Verifying that the change actually took

The cheapest confirmation is an outside one: our free GPC checker loads a URL with the signal set and reports whether third-party trackers still fire. That is the question enforcement turns on, and it is the one your own logs cannot answer: a log shows the header arrived, not that anything stopped.

A verification worth trusting has four properties:

  • A clean session. Fresh profile, nothing cached from an earlier test, no leftover consent state. The browser profile that produced the failure is the least reliable place to confirm the fix.
  • A signal-on and signal-off comparison. Same page, two captures, compared request by request. Identical network output across both means the site is not branching on the signal at all.
  • More than the homepage. Checkout flows, landing pages, and anything built outside the main template are where hardcoded tags concentrate.
  • Evidence you can keep. A list of request URLs that fired while the signal was active is something you can hand to a vendor or attach to a ticket. A recollection that it looked fine is not.

When it still does not work

A first verification that comes back with trackers still firing is an ordinary result. It usually means one layer is not doing what it was told, and the candidates are finite. The efficient move is to work the causes in order of likelihood rather than re-reading the consent rules first, since the rules are seldom where the problem sits.

The larger point is that a passing verification is a snapshot. This implementation is correct on the day it ships, and then the site keeps changing: a theme update adds a tag, a campaign page ships outside the usual template, a new tag lands in the container from someone who never saw the group rule. None of that touches the consent configuration, and all of it changes what a GPC-on page load does.

So treat verification the way you treat any other regression check. Run it on a schedule, run it after theme and template changes, and run it against more than one URL. The three steps above are the implementation. The recurring check is what keeps the implementation true.

Check this on your own site

Our free GPC checker loads one URL with the Global Privacy Control signal set and reports whether third-party trackers still fire.

Run a free GPC check