Consent · PMs · engineers
CAPI still needs a lawful basis
Server-side is better at surviving blockers. It is worse at inheriting a CMP that only gated JavaScript. If the user refused ads storage, your webhook still knows the email.
Pass the choice
Store consent with the session or the order, including the TC String and GPP if you have them. Skip CAPI, or send a redacted event, when the basis is missing. Silent always-on CAPI is how you rebuild the tag the user turned off. Consent Mode updates gtag. It does not update your shop backend.
A retry three days later with a new yes you never received is a new processing. If the user refused ads, do not send the CAPI Purchase with fbp, fbc, and em and call it a measurement-only event unless counsel said that sentence. Dropping click ids but keeping email is still a match attempt.
Hashing is not consent
SHA-256 of an email is still personal data. It is a matching technique. It does not replace notice, contract, or legitimate interest analysis. IP and user-agent on a CAPI body are identifiers too. Treating CAPI as outside the banner because it never runs in the browser is the usual way this leaks.
Pixellint will not decide lawful basis. It will still tell you when the payload is an unhashed email or a hashed IP, which is a matching bug and a processing bug stacked. vendor.meta-conversions-api.body.unhashed_email and vendor.meta-conversions-api.body.hashed_plaintext_field are those two. Send what the vendor needs for the event you are allowed to send. Match quality is not a legal basis.
{
"data": [{
"event_name": "Purchase",
"event_time": 1770000000,
"action_source": "website",
"user_data": {
"em": ["shopper@example.com"],
"client_ip_address": "203.0.113.42"
}
}]
}
The vendor 200 is not a legal opinion
Graph API returning 200 means Meta accepted the JSON. It does not mean you had a basis to send it. Measurement Protocol returning 204 means Google accepted the hit. It does not mean analytics_storage was granted. Do not paste a 200 into a DPIA.
pixellint validate json will tell you if event_time is 13 digits, if action_source is missing, if em is raw, if client_ip_address is a digest. It will not tell you the CMP said no. Keep the CMP record next to the CAPI log. If those two disagree, the log is the leak.
Processors
The ads platform is a recipient. Your CAPI proxy and sGTM host are processors or controllers depending on how you run them. Name them in the policy you already claim is accurate. Many CAPI endpoints do not have gdpr_consent query fields. Then the record of consent lives in your log and in the CMP, and the payload should not include fields you were not allowed to process.
event_source_url, client_ip_address, and hashed PII are the usual over-collection. Do not copy the full user table into user_data because Event Match Quality went up in a dashboard. Gate the whole event, or send a genuinely non-ads pipeline that is not Meta's graph.
Region and action_source
action_source=website, email, app, phone_call, chat, physical_store, system_generated, business_messaging, and other are Meta's nine documented values. They describe where the event happened. They do not describe the lawful basis. A physical_store purchase in the EEA is still in GDPR. An app event after ATT deny can still be a CAPI event with hashed email if you have a basis. It cannot invent an IDFA the OS withheld.
Google Analytics Measurement Protocol is the same split. non_personalized_ads is deprecated in favor of the consent object. Pixellint flags that deprecation on the GA4 pack as vendor.google-analytics.body.non_personalized_ads.deprecated. Wiring the consent object is still your job. The 16-digit timestamp_micros check is a clock check, not a GDPR check.
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.