İçeriğe atla
Eğitimler

Server-Side Tagging and Consent: How to Enforce Cookie Choices After the Request Leaves the Browser

Server-side tagging moves vendor requests out of the browser, and out of reach of browser-level script blocking. This guide shows how consent signals travel to a server container, how to gate vendor tags there, and how to verify enforcement independently with a consent state API.

12 minutes read 8 read

Short answer: Server-side tagging moves vendor calls from the visitor's browser into a server container you operate, which means browser-level script blocking no longer covers them. The consent choice still travels with each hit: Google's Consent Mode encodes it into the gcs and gcd parameters that arrive at your server container. Enforcement, however, is your responsibility: nothing forces a third-party tag inside the container to consult those signals before firing. A complete setup has three layers: forward consent state with every hit, gate each vendor tag in the container with default-deny conditions, and verify independently against the recorded consent state through a server-side API.

Server-side tagging is winning adopters for good reasons: fewer scripts in the page, faster load times, first-party endpoints, and full control over what data leaves your infrastructure. But the same property creates a consent blind spot. When Meta, TikTok or an affiliate network is called from your tagging server instead of the visitor's browser, browser-based consent tooling never sees the request. A banner that blocks scripts perfectly on the client can coexist with a server container that ignores the visitor's choice entirely.

This guide covers what happens after the browser: how consent reaches the server container, how to make server tags respect it, and how to prove it works. The browser-side wiring (default state, update calls, tag consent settings in web GTM) is covered in our Consent Mode v2 GTM setup guide; here we assume that part is in place.

Note: This article is technical documentation, not legal advice. Which consent rules apply to your traffic depends on the laws relevant to your audience; consult a qualified lawyer for decisions about your specific situation.

Table of contents

Why server-side tagging changes the consent picture

In the classic client-side model, every vendor runs in the browser, and enforcement is straightforward: the consent tool keeps non-essential scripts inert until the visitor decides, web GTM applies per-tag consent checks, and anyone can open DevTools and watch which requests fire.

Server-side tagging breaks that symmetry. The browser sends a single stream of first-party hits to your tagging server (typically a subdomain like sgtm.example.com), and the server container fans those events out to Google Analytics, Meta's Conversions API, ad platforms and data warehouses. Those outbound requests originate from your infrastructure; nothing running in the browser can observe or suppress them.

Two consequences follow:

  1. Browser-level script blocking no longer covers the full picture. It still matters for client-side scripts, but it cannot reach a server-to-server Conversions API call.
  2. The legal question does not move. Consent-first regimes such as the GDPR with the ePrivacy Directive, and KVKK in Turkey, care about whether the processing had a valid basis, not which machine emitted the request. (For how the two regimes differ on consent specifics, see our KVKK vs GDPR comparison.)

So the enforcement point has to move with the requests: into the server container itself, and into any backend process that talks to vendors on the visitor's behalf.

The architecture: how consent travels from browser to server container

The consent choice does not get lost on the way to the server. In a standard Google setup, three layers cooperate:

Layer Role Who is responsible
Browser CMP Collects the choice, fires Consent Mode default and update Your consent tool
The hit itself Carries consent state as URL parameters (gcs, gcd) to the server container Google tag, automatically
Server container Reads the signals and decides which vendor tags may fire You

The consent banner records the visitor's decision and translates it into Consent Mode signals (the conceptual background is in our Google Consent Mode overview). From that point on, every hit the Google tag sends carries the current consent state with it.

The compact carrier is the gcs parameter. Its format is G1 followed by two binary digits: the first for ad_storage, the second for analytics_storage, where 0 means denied and 1 means granted:

Value ad_storage analytics_storage Typical meaning
G100 denied denied Visitor rejected, or no choice yet under a denied default
G101 denied granted Analytics accepted, marketing rejected
G110 granted denied Marketing accepted, analytics rejected
G111 granted granted Everything accepted

Alongside it, the gcd parameter carries a more detailed encoding that covers all four Consent Mode v2 signals, including ad_user_data and ad_personalization, and how each was set. When the Google tag on your page sends to a server container URL, these parameters arrive with each request, and the container's preview mode shows them on the incoming hit.

That is where Google's automation ends. The signals arrive; whether anything inside the container respects them is a configuration question, and it is yours.

Step 1: Forward consent to the server container

If you use the standard Google tag (gtag.js or the GA4 tag in web GTM) pointed at your server container, consent forwarding comes with it: once the Consent Mode default and update commands fire correctly in the browser, the consent parameters are included on the hits automatically. There is nothing extra to build, but there is something to verify:

  1. Open your server container's preview mode and load the site in another tab.
  2. Reject everything in the banner.
  3. Select an incoming request in the preview timeline and inspect its parameters: you should see gcs=G100 (and a gcd value) on the request.
  4. Accept everything and repeat: the new hits should show gcs=G111.

If those parameters are present and change with the visitor's choice, forwarding works.

The picture is different with custom clients and non-standard transports. A hand-rolled fetch that whitelists known parameters, a proxy or CDN rule that rewrites query strings, or a redirect hop can silently strip gcs and gcd; events still arrive and look healthy, but every hit is now consent-anonymous. Check the raw incoming request in preview, not just the parsed event, to confirm the parameters survived the trip.

One more thing to forward: a visitor identifier. For server-side consent lookups (Step 3), the container needs to know which visitor an event belongs to in your consent records. With the cerez.io SDK, the visitor id lives in a first-party cookie named cb_vid; read it in the browser and attach it to your events as a parameter.

Step 2: Gate outgoing vendor tags in the server container

Inside the server container, adopt one rule and apply it everywhere: default deny. A vendor tag fires only on positive evidence that the relevant consent exists. Absence of a signal, an unparseable value, an unknown visitor: all of these mean the tag stays silent.

The mechanics in sGTM:

  1. Expose the consent state as a variable. Create a variable that reads the consent information from the parsed event: the gcs digits, or a custom category parameter your CMP forwards with the hit.
  2. Add conditions per vendor tag. Use trigger conditions or blocking exceptions so that each outbound tag checks the category it depends on. Marketing endpoints (Meta Conversions API, ad platforms, retargeting partners) require the advertising signal granted. Analytics and measurement endpoints require the analytics signal granted.
  3. Map categories deliberately. A typical mapping: your banner's marketing category gates ad_storage-dependent tags, the analytics category gates analytics_storage-dependent tags. Tags serving strictly necessary functions are the only candidates for firing without consent, and that list should be short and documented.

Two limits to keep in mind. First, gcs encodes exactly two storage signals; if your banner distinguishes more categories (functional or preference, for instance), that granularity is not in gcs, so forward the categories explicitly as event parameters or resolve them with a state lookup. Second, conditions only exist where you add them: a container is only as compliant as its newest tag, so make consent conditions part of the definition of done for every tag added later.

Step 3: Verify independently with a consent state API

Forwarded signals tell you what the browser asserted at the moment of the hit. They cannot answer a different question: what does the consent record say for this visitor, right now? For that, ask the system that stores the consent logs.

cerez.io exposes this as a server-side endpoint, built for exactly this sGTM use case. It is part of the same public API surface the banner uses, keyed by your site's API key:

GET https://cerez.io/api/v1/consent/state/{api_key}?vid={visitor_id}

vid is the visitor identifier the cookie consent banner assigned when it recorded the choice (the cb_vid first-party cookie from Step 1; the parameter is also accepted as visitor_id). A worked example:

curl "https://cerez.io/api/v1/consent/state/your_api_key?vid=7f3a2c91-4b0e-4d2a-9c1f-8e5d6a7b0c12"
{
  "found": true,
  "expired": false,
  "action": "partial",
  "categories": {
    "necessary": true,
    "functional": false,
    "analytics": true,
    "marketing": false
  },
  "given_at": "2026-08-09T14:32:11+03:00",
  "expires_at": "2027-08-09T14:32:11+03:00"
}

The response contract:

Field Type Meaning
found boolean Whether a consent record exists for this visitor on this domain
expired boolean Whether the most recent consent has passed its expiry date
action string The last recorded action: accept_all, reject_all, partial or update
categories object What the banner recorded; with the cerez.io banner, a map of necessary, functional, analytics, marketing to true or false
given_at ISO 8601 When the consent was given
expires_at ISO 8601 When it expires, following the domain's configured consent duration

When no record exists for the visitor, the endpoint returns {"found": false, "action": null, "categories": []}. A missing vid returns a 422, and an unknown or inactive API key returns a 404. The enforcement rule your server code derives from this is one line of logic:

allow_marketing = found AND NOT expired AND categories.marketing == true

Everything else, including found: false and expired: true, means treat the visitor as unconsented. The endpoint sits behind the same rate limiting as the rest of the SDK API (500 requests per minute per API key), which is one practical reason to cache lookups briefly rather than querying on every event; the FAQ covers safe caching.

When to query state server-side, and when forwarded signals are enough

You do not need a state lookup on every event. The two mechanisms answer different situations:

Situation Forwarded signals (gcs/gcd) State API lookup
Live hit stream through sGTM Yes, each hit carries fresh state Not required
Webhook from a payment or booking provider No browser context exists Yes
CRM enrichment or audience sync jobs No hits involved Yes
Offline conversion uploads Conversion happens away from the site Yes
Batch or scheduled backend jobs No browser context Yes
Finer categories than the two gcs signals Not carried Yes, full category map
Consent expiry awareness Not carried Yes, expired and expires_at
Auditing that forwarding works Source of the claim Independent cross-check

The pattern to remember: forwarded signals govern the synchronous path, lookups govern the asynchronous one. A live hit carries current consent state and can be gated on directly; a webhook, a nightly conversion upload or a CRM job has no browser in the loop, so the recorded consent state is the only thing you can legitimately consult. The lookup is also the cross-check that turns belief in forwarding into audited evidence.

Testing enforcement end to end

Test the whole chain in the container's preview mode with four scenarios, judging each by outgoing requests, not tag status alone:

  1. Reject all. Decline everything in the banner, then generate events (page views, a test conversion). In sGTM preview, the incoming hits should show gcs=G100, marketing and analytics vendor tags should not fire, and the container's outgoing request log should contain no calls to those vendor endpoints.
  2. Partial consent. Accept analytics only. Incoming hits show gcs=G101; analytics endpoints receive requests, marketing endpoints stay silent.
  3. Accept all. Everything fires; this confirms your conditions do not overblock.
  4. Fresh visitor. In a clean private window, generate events before touching the banner. Under a correct denied default, hits carry gcs=G100 and vendor tags stay silent. This is the scenario that catches default-grant misconfigurations.

The reason to insist on the request log: a tag can report a fired status while a template-level check suppressed the network call, and the reverse can happen with custom templates; the log is the ground truth of what actually left your server.

Re-run the four scenarios after every container change, because a newly added tag without consent conditions reintroduces the bypass silently. And check the browser side of the ledger with a cookie scanner periodically: it shows which cookies are actually set before and after consent on the page itself, which server-side testing does not cover.

Common failure modes

Five patterns account for most real-world consent bypasses in server-side setups:

  1. The container fires vendors unconditionally. The default state of a freshly built sGTM container: events arrive, tags fire, nothing checks consent. Symptom: vendor endpoints receive requests even in a reject-all test. Fix: audit every tag for a consent condition; treat a tag without one as a defect.
  2. A custom client drops the consent parameters. A parameter whitelist, a CDN rewrite or a redirect hop strips gcs/gcd in transit. Symptom: incoming requests in preview show no consent parameters for any visitor. Fix: inspect the raw incoming request, then widen the whitelist or adjust the rewrite so the parameters survive.
  3. Stale cached state. A backend caches a consent lookup for days; the visitor withdraws consent; the server keeps sending data on the old answer. Symptom: enforcement lags withdrawal. Fix: keep cache TTLs in minutes, and treat every cache miss as denied until refreshed.
  4. Missing signal treated as granted. Somewhere a condition reads "fire unless explicitly denied." Every dropped parameter then becomes implicit consent. Fix: invert the logic everywhere to default deny; absence of evidence is denial.
  5. Expiry ignored. The consent record exists but has passed its expiry date, and the server keeps acting on it. Symptom: expired: true in the state response while vendor calls continue. Fix: include NOT expired in every enforcement rule, as in the one-line rule in Step 3.

Frequently Asked Questions

Does Consent Mode work automatically with server-side GTM?

Short answer: Partially. The Google tag forwards the consent state with every hit to your server container automatically once the browser-side default and update commands are wired, and Google's own components pass those signals along toward Google endpoints. What is not automatic is enforcement for everything else: third-party vendor tags in the server container (Conversions APIs, ad platforms, custom endpoints) do not consult the forwarded signals on their own. You have to add consent conditions to each of them, which is Step 2 of this guide.

Do I need a consent state API if I already forward gcs and gcd?

Short answer: Not for the live hit stream: events flowing browser to container carry current consent state, and gating on those parameters is sufficient there. You need a state lookup for everything that happens without a browser in the loop: webhooks, CRM enrichment, offline conversion uploads, scheduled jobs. It also gives you what the forwarded parameters cannot: the full category map beyond the two gcs signals, the expiry status, and an independent record to audit your forwarding against.

Can I cache consent state on my server?

Short answer: Yes, briefly. Caching per visitor id for a few minutes keeps you comfortably inside the API rate limit (the cerez.io endpoint shares the SDK API's 500 requests per minute per key) without meaningfully delaying enforcement. The safety rules: keep the TTL short, never serve a cached "granted" past its expires_at, and treat a cache miss or an API error as denied rather than falling back to the last known positive answer. A withdrawal must take effect within your TTL window, so choose it accordingly.

What should my server do when consent has expired?

Short answer: Treat the visitor exactly like one who never consented: suppress all non-essential vendor calls. The state endpoint surfaces this explicitly with expired: true and the expires_at timestamp, so the check is one condition in your enforcement rule. Do not delete or overwrite the historical record; the expired log remains your evidence that consent existed for the period it covered. On the visitor's next visit, the banner re-prompts and a fresh record replaces the expired state going forward.

Sources


Running a server container and want the consent side handled? Start free: the cerez.io banner records consent with a visitor id out of the box, and the consent state endpoint is available for your sGTM and backend enforcement from day one, no credit card required. Plan details are on the pricing page. This article is for information purposes and is not legal advice.

Author
cerez.io

cerez.io içerik ekibi; KVKK, GDPR, WCAG 2.2 ve EAA uyumluluk uzmanı. Balıkesir merkezli Türkiye'nin uyumluluk platformu.

Turkey's Compliance Platform

Achieve the compliance in this article right away

cerez.io with in 5 minutes Become KVKK, GDPR, WCAG 2.2 and EAA compliant. 14-day Pro trial, no credit card required.

No credit card · Data hosted in Turkey · Turkish-language support · KVKK assurance

⚡ LEGAL OBLIGATION Presidential Circular 2025/10: For public institutions, municipalities, banks, universities, hospitals and schools, the WCAG 2.2 A obligation is now in force (the deadline passed on 21 June 2026) · E-commerce deadline: 21 June 2027