Skip to content
← ALL WRITING

2026-04-22 / 14 MIN READ

Consent Mode v2 and CAPI - what actually fires on your server

A pattern-library breakdown of consent mode v2 capi behavior across four consent states, with the DTC failure modes each one produces on Meta attribution.

Google Consent Mode v2 introduces four consent signals (ad_storage, ad_user_data, ad_personalization, analytics_storage) and expects you to pass them through your GTM containers into whatever event destinations you have wired. Meta CAPI is one of those destinations. Most DTC stores I audit configure Consent Mode v2 in GTM but never actually route the signals into the CAPI handler. The events still fire. They just fire with the wrong privacy posture.

I cleaned up this pattern on three different stores during the same stretch of Q2 2024 rebuilds I keep referencing. Same shape every time: Consent Mode v2 was "installed" in the sense that the CMP was emitting the signals, but the server-side CAPI handler treated every event identically. Matching behavior was either too permissive (firing full PII for visitors who had declined ad_user_data) or too restrictive (dropping events entirely for visitors who had declined only ad_storage).

This post walks what consent mode v2 capi behavior actually looks like in production, three DTC instances where it broke silently, and how to route the signals so the CAPI handler respects them.

Consent Mode v2 lives on the browser. The CMP (OneTrust, Cookiebot, Didomi, or Google's own consent banner) captures the visitor's choices and emits the four signals as a JavaScript state that GTM can read. GTM can then gate browser-side tags on the signals, and can also pass the signals along as parameters on any server-side event it forwards.

The CAPI handler, whether it lives in GTM server container or in your own serverless endpoint, has no direct access to the visitor's CMP state. The only way it knows the consent signals is if the upstream system passes them through.

On a majority of stores, this handoff is either missing or partial. GTM knows. The CMP knows. The CAPI handler does not.

Consent state matrix
ad_storage: granted
ad_storage: denied
ad_user_data:
granted
ad_user_data:
denied
state 1storage ✓ · user data ✓

Full fidelity. Match quality ~9 possible.

The four consent states Google Consent Mode v2 can send to your server. Click a cell to hold it.

How the four signals actually affect CAPI

Consent Mode v2 ships four signals. Only two of them meaningfully affect CAPI behavior.

SignalWhat it controlsCAPI implication
ad_storageBrowser cookies for advertising (fbp, fbc, IDFA)Denied = no fbp/fbc to forward; server can still fire with PII
ad_user_dataSending user data (email, phone, IP) to ad platformsDenied = do not send hashed PII on the CAPI payload
ad_personalizationUsing data for remarketing and personalized adsInformational; Meta uses it for how data is retained, not whether event fires
analytics_storageAnalytics cookies (GA4 specifically)Does not affect CAPI; GA4 only

The actionable pair is ad_storage (cookies) and ad_user_data (identifiers). The four combinations of these two produce four different CAPI behaviors, and the matrix above makes the states concrete.

The store: a DTC wellness brand, Shopify, EU traffic through Cookiebot, server-side CAPI.

The symptom: during a compliance review, the team discovered that EU visitors who had declined ad_user_data but accepted ad_storage (a common partial consent in the EU) were still having their hashed email, phone, and address sent to Meta via CAPI.

The shape: the browser pixel respected the CMP correctly. Decline ad_user_data, no browser events fire with PII. The server-side CAPI handler, which ran in a GTM server container triggered by the Shopify orders/create webhook, never saw the consent signal at all. It fired full PII on every purchase regardless of visitor consent state.

The partial-consent case is common in the EU. Visitors often accept cookies (for site function) but decline the more specific ad_user_data signal. The CAPI handler needs to respect the finer-grained choice.

The fix: forward the consent signal from the browser into the order's note attributes or a custom field so the server webhook can read it. In the Shopify storefront, write the Consent Mode v2 state into note_attributes on checkout extension or cart attributes. In the webhook handler, read those attributes and conditionally strip PII from the CAPI payload:

function respectConsent(order: ShopifyOrder, userData: UserData) {
  const adUserData = order.note_attributes?.find(
    (a) => a.name === "_cm_ad_user_data",
  )?.value;
  const adStorage = order.note_attributes?.find(
    (a) => a.name === "_cm_ad_storage",
  )?.value;

  const adUserDataGranted = adUserData === "granted";
  const adStorageGranted = adStorage === "granted";

  return {
    em: adUserDataGranted ? userData.em : undefined,
    ph: adUserDataGranted ? userData.ph : undefined,
    external_id: adUserDataGranted ? userData.external_id : undefined,
    ct: adUserDataGranted ? userData.ct : undefined,
    st: adUserDataGranted ? userData.st : undefined,
    zp: adUserDataGranted ? userData.zp : undefined,
    country: adUserDataGranted ? userData.country : undefined,
    fn: adUserDataGranted ? userData.fn : undefined,
    ln: adUserDataGranted ? userData.ln : undefined,
    fbp: adStorageGranted ? userData.fbp : undefined,
    fbc: adStorageGranted ? userData.fbc : undefined,
    client_ip_address: userData.client_ip_address, // IP is always transmitted
    client_user_agent: userData.client_user_agent,
  };
}

IP address and user agent are almost always transmitted on CAPI events; they are technical necessities for the event to reach Meta at all. They are also the signals that Meta uses for modeled conversions when explicit PII is denied.

EU compliance cleared. Reported Meta conversions dropped about 8% for EU traffic, which was the actual reduction in attributable events once consent was honored. That was the expected honest result. Match quality on remaining EU events stayed at 9.

The store: a US-only DTC apparel brand, Shopify Plus, no CMP at all.

The symptom: legal review flagged that California visitors under CCPA have a right to opt out of the "sale or sharing" of personal information, which includes CAPI transmission to Meta. The store had no mechanism to handle this.

The shape: the team had interpreted "US-only" as "no consent layer needed." They skipped Consent Mode v2 entirely and wired CAPI to fire on every event. Without any CMP, there was also no opt-out mechanism. A CCPA "Do Not Sell My Info" link existed in the footer, but clicking it had no effect on CAPI.

The fix: install a lightweight CMP (Google's consent banner is free and supports Consent Mode v2), default all signals to granted for US visitors (CCPA allows implied consent until opt-out), but expose a working opt-out that sets ad_user_data and ad_personalization to denied. Route the signals through to the CAPI handler the same way as Instance 1.

Default behavior stays the same for 99% of visitors. For the 1% who actively opt out, the CAPI handler strips PII. Compliance risk goes down sharply. Revenue impact: nil.

Instance 3: Third-party browser blocks cookies, CAPI falls back gracefully

The store: a DTC supplements brand, Safari-heavy traffic, strict ITP environment.

The symptom: Safari visitors were showing up in Events Manager with 0% fbp and 0% fbc coverage. ad_storage showed as granted in the CMP (visitors accepted cookies), but the cookies were not persisting. Match rate on Safari traffic was 40% below Chrome.

The shape: this was not actually a Consent Mode v2 problem. It was a cookie-persistence problem that was being misread as a consent problem. Safari's ITP expires third-party cookies (including fbp and fbc if they are set on a non-first-party domain) within 7 days. Consent Mode v2 correctly reported ad_storage: granted, but the cookies themselves had been wiped by the browser before the CAPI event fired.

The fix: two moves. First, move fbp and fbc to first-party cookies by configuring a custom loader domain (via Stape or your own subdomain pointing at a GTM server container). First-party cookies survive ITP's 7-day limit. Second, when fbp/fbc are missing from the server event despite ad_storage: granted, do not infer consent decline. Fire the CAPI event with PII and let Meta use IP + UA + email hash for matching. The visitor consented to ad_user_data, and the missing cookies are a technical artifact, not a consent signal.

I covered the custom-loader-domain pattern in the match quality 9 tutorial. The broader cookie-persistence story is in the iOS 17 ATT post.

Match rate on Safari climbed to parity with Chrome inside three weeks.

Consent Mode v2 lives on the browser. The CAPI handler, by default, has no idea what the visitor chose.

What the pattern tells us

Three instances, three different surface failures, one underlying structural gap: Consent Mode v2 signals stop at the browser unless your code explicitly carries them to the server. The CMP does its job, GTM forwards the signals to browser tags, and the server-side CAPI handler fires on whatever schedule its own trigger produces, with no awareness of the consent state the visitor just chose.

The fix is architectural, not configurational. Pass the Consent Mode v2 state through your order-creation flow (cart attributes, note attributes, or a custom webhook payload field) so the server handler can read it. Apply the signal to decide which PII fields to include on the CAPI payload. This works for GDPR, for CCPA, and for any future privacy regime that lands on a similar four-signal shape.

Second observation: consent is not a binary fire-or-not-fire switch. It is a set of four signals that combine into at least four distinct states, each producing different CAPI behavior. Treating it as binary (which is what most implementations do) either over-transmits PII or under-fires events. The matrix is the right mental model.

How to spot it early

Four signals that Consent Mode v2 is wired wrong on your CAPI setup.

CAPI events fire for every visitor regardless of CMP choice. Easy to check: open your server logs during a session where you actively decline ad_user_data. If the CAPI event still includes hashed email, you have the Instance 1 pattern.

GTM shows consent signals but your CAPI handler doesn't reference them. Search your server-side CAPI code for any reference to ad_user_data, ad_storage, consent, or consent_state. If there are no hits, the signals are not being honored.

EU compliance review has flagged your CAPI transmission without a fix landing. This is the clearest signal. A DPA complaint is usually preceded by an internal flag; if the flag has sat unresolved, the root cause is almost always the handoff gap.

Match rate on Safari is more than 30% below Chrome. Not a consent issue directly, but a signal that your fbp/fbc cookies are not first-party. Fix that alongside any Consent Mode v2 work; the two problems share an infrastructure boundary.

The CAPI Leak Report checks 4 and 5 specifically look at consent propagation and cookie persistence. It is the scan I wrote to catch this class of regression on stores that thought they had shipped Consent Mode v2 correctly.

The deeper legal context (why hashed PII without consent is still legally problematic under GDPR) is in the Klaviyo CAPI consent post. For telehealth and other regulated intake flows, the stricter consent capture pattern is in the consent capture telehealth post.

Consent routing is one section of a larger stack story; the rest of the patterns live in the full Meta CAPI pattern library. If you are also trying to figure out which attribution windows still hold after iOS 17 and the Android rollout, the field notes on attribution windows in 2026 pair well with this piece.

The minimal implementation

Three moves to land this pattern in a DTC stack.

Capture the Consent Mode v2 state on the browser and write it into the cart or order at checkout:

// On checkout page, after CMP has resolved
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: "consent_snapshot",
  consent: {
    ad_storage: window.google_tag_data?.ics?.getConsentState("ad_storage"),
    ad_user_data: window.google_tag_data?.ics?.getConsentState("ad_user_data"),
    ad_personalization: window.google_tag_data?.ics?.getConsentState(
      "ad_personalization",
    ),
  },
});

// Write to Shopify cart as note attributes before checkout submit
fetch("/cart/update.js", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    attributes: {
      _cm_ad_storage: "granted", // or "denied", pulled from consent snapshot
      _cm_ad_user_data: "granted",
      _cm_ad_personalization: "granted",
    },
  }),
});

Read the state in your server webhook handler and route it into buildUserData:

const userData = respectConsent(order, buildUserData(rawInputs));

Log the consent state (not the PII) on every CAPI event for audit:

logger.info("capi.purchase.fired", {
  event_id: event.event_id,
  consent: {
    ad_storage: order.note_attributes?.find((a) => a.name === "_cm_ad_storage")
      ?.value,
    ad_user_data: order.note_attributes?.find(
      (a) => a.name === "_cm_ad_user_data",
    )?.value,
  },
  user_data_fields_present: Object.keys(userData).filter(
    (k) => userData[k as keyof typeof userData] !== undefined,
  ),
});

If a regulator ever asks for an audit trail, you have per-event consent logs going back the length of your log retention. That is the honest receipt that privacy engineering requires.

FAQ

Do I need Consent Mode v2 if I only operate in the US?

You need a consent layer, but it does not have to be Google Consent Mode v2 specifically. CCPA and state privacy laws require an opt-out mechanism for sharing personal information, and CAPI transmission qualifies. Google's consent banner is the cheapest way to get it because it integrates with GTM and routes into Consent Mode v2 natively.

Does denying ad_user_data mean Meta sees nothing?

No. Meta still receives the event with IP address, user agent, and modeled conversion signals. The hashed PII (email, phone, address) is absent, which lowers match quality but does not stop the event. Meta then uses modeled conversions for that visitor.

What happens if the consent state is missing from the order payload entirely?

Default to the more conservative state. For EU visitors (detect via IP country), default to denied on everything sensitive. For US visitors, default to granted under CCPA implied consent until opt-out. Do not default to granted globally; it is a compliance risk.

Does Consent Mode v2 change how fbp and fbc work?

Only indirectly. If ad_storage: denied, those cookies should not be set at all, and you should not forward values you already have (you shouldn't have them). If ad_storage: granted, forward them normally. The cookies are just data; the consent signal governs whether your code writes or reads them.

Can I use Consent Mode v2's modeled conversions to recover attribution for denied visitors?

Yes, and that is the design. When PII is absent because the visitor declined ad_user_data, Meta and Google both offer modeled conversion estimates based on IP, UA, and aggregate behavior. The estimates are less accurate than matched events but better than zero.

Do I need to do anything special for Apple's App Tracking Transparency?

ATT affects iOS apps and Safari on iOS. For a DTC web store, the relevant mechanism is Intelligent Tracking Prevention (ITP), not ATT. Consent Mode v2 handles the consent layer; first-party cookies handle the persistence layer. Both are needed. The iOS 17 ATT post covers the Safari-specific behavior.

Sources and specifics

  • Three instances drawn from Q2-Q3 2024 Shopify DTC engagements, anonymized per the casebook. Primary rebuild context in the tracking gap case study.
  • Google Consent Mode v2 signal list (ad_storage, ad_user_data, ad_personalization, analytics_storage) is documented in Google Tag Manager's consent API reference as of April 2026.
  • GDPR treatment of hashed PII as pseudonymous (not anonymous) data is established under Article 4(5) and recital 26; referenced by most EU DPA guidance as of late 2024.
  • CCPA "implied consent until opt-out" is valid under the current California Privacy Rights Act regulations; other states (Colorado, Virginia) have similar structures.
  • Cookie persistence under Safari ITP: third-party cookies expire within 7 days of last first-party interaction; first-party cookies set via a custom loader domain survive indefinitely (until the browser's broader clearing rules apply).

// related

DTC Stack Audit

If this resonated, the audit covers your tracking layer end-to-end. Server-side CAPI, dedup logic, and attribution gaps - all mapped to your stack.

>See what is covered