Dilshat Rakhimov
July 2026 · 10 min read

Here's a sentence I've heard on more intro calls than I can count, usually from a competent engineer:

"We're going server-side, so the consent stuff is handled."

It isn't. Going server-side changes where a tag fires. It changes nothing about whether it was allowed to. And it quietly removes three of the safety nets that used to cover for a sloppy consent setup, which is why a server-side migration is the single most common way I see a previously-fine consent implementation turn into a leaking one.

This is what actually has to be wired, how to test it in the state nobody tests, and the second half of the problem - what you're sending - that consent doesn't cover at all.

The four signals, briefly

Consent Mode v1 had two: ad_storage and analytics_storage. v2 added ad_user_data and ad_personalization, and Google began enforcing v2 for EEA traffic in March 2024.

SignalControls
ad_storageStorage of advertising identifiers (cookies)
analytics_storageStorage of analytics identifiers
ad_user_dataWhether user data may be sent to Google for advertising
ad_personalizationWhether that data may be used for personalised advertising / remarketing

The split matters for the server-side conversation. The first two are about storage in the browser - the browser is where they take effect. The two v2 additions are about transmission and use, and transmission is exactly what a server container does for a living. A user can grant analytics_storage and deny ad_user_data, and if your server container isn't reading that distinction, it will happily forward the event to Google Ads anyway.

How consent is supposed to reach the server

Google's server-side documentation lays out three steps:¹

  1. "The consent banner on your website receives the user's consent choices and sends them to the Google tag."
  2. "The Google tag sends the user's preferences to the server container by adding consent parameters to the HTTP request."
  3. "Google product tags in the server are consent-aware and adjust the amount and kind of data they send based on the user's preferences."

On the wire, that's the gcs and gcd parameters. gcs is the v1 encoding - G1xy, where x is ad-cookie consent and y is analytics-cookie consent, each 1 or 0. gcd is the v2 successor and encodes all four signals plus how each state was arrived at; it rides along on hits to Google services even when Consent Mode isn't active at all.²

This is the first thing to check on any inherited setup. Open the server container preview, pick an incoming request, and look at the raw query string. If gcs and gcd aren't there, the browser never told your server anything, and step 3 has nothing to act on.

The sentence everyone misreads

Google's page also says, plainly: *"you only need to set up consent mode in the web container."*¹

That sentence is true, and it is true about Google's own tags. GA4, Google Ads, Floodlight - those have built-in consent checks and will modulate their own behaviour off gcs/gcd without you configuring anything server-side.

It is not true about anything else in your container. Meta CAPI, TikTok Events API, Snapchat CAPI, a Klaviyo tag, your own custom HTTP request tag - none of these have built-in consent checks. They fire when their trigger fires. If the trigger is "GA4 event received", they fire on every event, from every user, including the ones who denied everything.

And GTM's own UI has a trap in it here. Each tag has an Additional Consent Checks setting with two options: No additional consent required and Require additional consent for tag to fire. The first option does not mean "this tag doesn't need consent". It means "GTM will not check anything before firing this tag" - the tag fires regardless.³ On a Google tag with built-in checks, that's fine, the tag polices itself. On a Meta CAPI tag, it's an open pipe.

Why server-side makes this worse, not better

Three safety nets you lose the day you migrate:

The browser can no longer block anything. Client-side, a user with an ad blocker or a strict CMP blocked the Meta pixel and the Google tag independently, at the source. Server-side, one allowed request to your first-party endpoint fans out to five vendors inside your container, and the browser has no visibility into any of it, let alone a veto. Whatever consent logic you didn't implement, nothing downstream is going to implement for you.

First-party subdomain removes the accidental backstop. That's the whole point of sGTM - your endpoint is data.example.com, ITP doesn't treat it as third-party, blockers largely don't touch it. The corollary is that the crude, blunt protections that used to catch a percentage of your mistakes now catch none of them.

The server container can see data the browser never could. CRM lookups, order margins, user IDs, hashed identifiers, lifetime value. That's the reason to run sGTM at all. It's also the reason a consent failure server-side is a materially bigger event than the same failure client-side: the payload is richer.

The four things to actually wire

1. Send the consent state. The CMP updates consent in the web container, and the Google tag carries gcs/gcd to the server on every request. Verify in the server preview, not in the CMP's own dashboard.

2. Map it into a variable. In the server container, create an Event Data variable reading the incoming consent state, so you have something to write conditions against. Name it explicitly - Consent — ad_user_data, not var3.

3. Gate every non-Google tag on it. Either add the variable as a trigger condition, or use Require additional consent for tag to fire with the right signals selected. Which signals depends on the vendor: an advertising CAPI needs ad_user_data at minimum, and ad_personalization if you're feeding remarketing audiences. Do this tag by tag. There is no container-level switch.

4. Test the denied state. This is the step that gets skipped on essentially every setup I've inherited. Set all four signals to denied, run a real purchase end to end, and watch outbound requests from the server container. If a single CAPI or Events API call leaves the box, you're not compliant - and you now have a reproducible test you can re-run after every container change. Consent setups don't fail at launch. They fail three months later when someone adds a tag and copies the trigger from an existing one.

The half consent doesn't cover: what's in the payload

Consent governs whether you may send. It says nothing about what you send. Those are two separate gates, and server-side is exactly where they compound - because the server container is where enrichment happens.

I worked on an insurance product inside a large bank's superapp where the analytics implementation was, by consent standards, fine. Users had agreed to analytics. The problem was the payload: the national ID number was riding in data.event_info.user.iin on every event, and the same field plus phone, first name and last name were duplicated onto user properties. National ID is a state-level identifier; local personal-data law prohibits passing it into systems unencrypted. A green consent banner does not make that legal, and no consent checkbox anywhere in the flow was ever going to catch it.

The fix has four parts, and they transfer directly to a server container:

Identity envelope. Analytics gets a UUID and nothing else. The UUID → national ID / name / phone mapping lives in the CRM, behind role-based access. Analysts who need to look at one specific user go CRM → UUID → analytics, and join back in BI only through a protected view that legal and compliance control. Behaviour and identity live in different systems with different access lists, and the join is a deliberate, logged act.

A sanitizer in the send path, not in the docs. One function that strips the known PII keys from every payload before it leaves, wrapping every track call - so the protection is structural rather than dependent on whoever writes the next event. In a server container, this belongs in the container, because that's where the CRM enrichment gets attached and where a helpful engineer will one day add customer_email in plaintext to debug something.

A sweep for what's already there. Removing the field from new events doesn't remove it from the user profiles it's already written to. That's an Identify API $unset pass over the existing user base - in that project, about 50,000 users, and the throughput planning was a real line item, not a footnote.

A gate that survives you. Blocked properties declared in the event schema so the platform rejects PII fields at ingest; a CI lint that greps the known PII keys and fails the build; and a dashboard counting PII-carrying events per day, with a target of zero. Acceptance criterion: zero PII fields in event properties, zero in user properties, user_id is a UUID rather than a legacy numeric ID.

Consent Mode v2 would have flagged none of that. It isn't designed to. It's a Google product requirement about advertising signals; your local personal-data statute is a separate obligation with a different scope and a different regulator, and in a lot of markets a stricter one.

A checklist you can run on an inherited container

  • gcs and gcd present on incoming requests in the server preview
  • Consent mapped to named Event Data variables in the server container
  • Every non-Google tag gated on the appropriate signal - listed tag by tag, not assumed
  • Denied-state test run end to end, zero outbound vendor calls, result written down
  • The denied-state test re-run as part of the release checklist for container changes
  • Payload audited for PII independently of consent - event properties and user properties
  • CRM enrichment in the server container reviewed field by field against what's actually needed
  • Blocked-properties schema and a CI lint for known PII keys
  • "PII events per day" monitor, target zero

The takeaway

Server-side tagging moves your tags off the user's browser and onto infrastructure you control. Everything that used to be someone else's enforcement - the blocker, the browser, the CMP's script blocking - becomes your code.

Two gates, wired separately. Consent decides whether the event may be sent. Data governance decides what may be in it. A setup that passes the first and fails the second is the one that ends up in a regulator's letter, and it's the more common of the two failures, because everybody tests the banner and nobody reads the payload.

Dilshat Rakhimov
Growth Analytics & Digital Architecture

¹ Implement consent mode with server-side Tag Manager — Google for Developers
² Consent Mode v2 for Google Tags — Simo Ahava
³ Tag Manager consent mode support — Tag Manager Help
Google Consent Mode v2 explained — Stape