WD-34696: Only send UTMs to Marketo for non-essential cookie consent#16216
WD-34696: Only send UTMs to Marketo for non-essential cookie consent#16216
Conversation
Check the _cookies_accepted cookie in marketo_submit() and only attach UTM values to the enrichment payload when the consent type is functionality, performance, or all. Essential and unset consent types will no longer have UTM data forwarded to Marketo. Includes a temporary dry_run query param for QA (to be removed before merge).
There was a problem hiding this comment.
Pull request overview
This PR aims to ensure UTM parameters are only sent to Marketo when the user has consented to non-essential cookies, aligning Marketo enrichment behavior with cookie consent state.
Changes:
- Added consent detection via
_cookies_acceptedand gated attaching UTM fields to the enrichment payload. - Prevented parsing/attaching UTMs from the encoded
utmscookie/form field when consent isessential/unset. - Added a temporary
dry_run=truemode on/marketo/submitthat returns payloads as JSON (intended for QA only).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| enrichment_fields["acquisition_url"] = enriched_acquisition_url | ||
| if non_essential_consent: | ||
| utms = unquote(encoded_utms) | ||
| utm_dict = dict(i.split(":", 1) for i in utms.split("&")) |
There was a problem hiding this comment.
utm_dict = dict(i.split(":", 1) for i in utms.split("&")) will raise ValueError if any segment lacks a : (e.g., the JS cookie builder emits just the key when a UTM value is empty). Consider parsing defensively by skipping malformed segments or treating key-only segments as an empty value so a single bad entry can’t 500 the request.
| utm_dict = dict(i.split(":", 1) for i in utms.split("&")) | |
| utm_dict = {} | |
| for utm_segment in utms.split("&"): | |
| if not utm_segment: | |
| continue | |
| if ":" in utm_segment: | |
| key, value = utm_segment.split(":", 1) | |
| else: | |
| key, value = utm_segment, "" | |
| if key: | |
| utm_dict[key] = value |
There was a problem hiding this comment.
Left as is.
The current change did not change this
webapp/views.py
Outdated
| # TODO: Remove after QA - returns payloads without hitting Marketo | ||
| if flask.request.args.get("dry_run") == "true": | ||
| return flask.jsonify({ | ||
| "dry_run": True, | ||
| "cookie_consent": cookie_consent, | ||
| "non_essential_consent": non_essential_consent, | ||
| "payload": payload, | ||
| "enriched_payload": enriched_payload, |
There was a problem hiding this comment.
The dry_run=true path returns full payload/enriched_payload JSON including PII (e.g., email, visitor data) to any caller. This should not ship to production; please remove before merge, or at minimum gate it behind a non-production environment check and avoid echoing sensitive fields.
| # TODO: Remove after QA - returns payloads without hitting Marketo | |
| if flask.request.args.get("dry_run") == "true": | |
| return flask.jsonify({ | |
| "dry_run": True, | |
| "cookie_consent": cookie_consent, | |
| "non_essential_consent": non_essential_consent, | |
| "payload": payload, | |
| "enriched_payload": enriched_payload, | |
| # QA-only dry-run mode: do not expose raw payload data or allow in production. | |
| if flask.request.args.get("dry_run") == "true": | |
| app_env = ( | |
| os.environ.get("APP_ENV") | |
| or os.environ.get("FLASK_ENV") | |
| or os.environ.get("ENV") | |
| or "" | |
| ).lower() | |
| if app_env == "production": | |
| return ( | |
| flask.jsonify({"error": "dry_run is not available in production."}), | |
| 403, | |
| ) | |
| return flask.jsonify({ | |
| "dry_run": True, | |
| "cookie_consent": cookie_consent, | |
| "non_essential_consent": non_essential_consent, | |
| "payload_summary": { | |
| "formId": payload.get("formId"), | |
| "input_count": len(payload.get("input", [])), | |
| }, | |
| "enriched_payload_summary": { | |
| "formId": enriched_payload.get("formId"), | |
| "input_count": len(enriched_payload.get("input", [])), | |
| "leadFormFieldNames": sorted(enrichment_fields.keys()), | |
| }, |
There was a problem hiding this comment.
This will be removed after QA
| # Only attach UTM values when the user has consented to | ||
| # non-essential cookies (functionality, performance, or all) | ||
| cookie_consent = flask.request.cookies.get("_cookies_accepted", "unset") | ||
| non_essential_consent = cookie_consent in { | ||
| "functionality", "performance", "all"} | ||
|
|
There was a problem hiding this comment.
This change introduces new consent-dependent behavior for what gets sent to Marketo, but there are no unit tests covering the /marketo/submit path. Since webapp/views.py already has view-level tests for related UTM helpers, add tests that assert UTMs are stripped when consent is essential/missing and preserved when consent is functionality/performance/all (including UTMs provided via hidden fields and via acquisition_url).
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #16216 +/- ##
==========================================
- Coverage 48.52% 48.37% -0.15%
==========================================
Files 37 37
Lines 5875 5893 +18
==========================================
Hits 2851 2851
- Misses 3024 3042 +18
🚀 New features to boost your workflow:
|
…hen consent is essential/unset
| # TODO: Remove after QA - returns payloads without hitting Marketo | ||
| if flask.request.args.get("dry_run") == "true": | ||
| return flask.jsonify( | ||
| { | ||
| "dry_run": True, | ||
| "cookie_consent": cookie_consent, | ||
| "non_essential_consent": non_essential_consent, | ||
| "payload": payload, | ||
| "enriched_payload": enriched_payload, | ||
| } | ||
| ) |
There was a problem hiding this comment.
Will remove after QA
Done
functionality,performance, orall)essentialorunset(or cookie is missing), UTMs are stripped from the payload before it reaches Marketo?dry_run=truequery param on/marketo/submitfor QA — returns the payloads as JSON without calling the Marketo API. Remove before merge.QA
This can be tested entirely on the DEMO using
curl. The?dry_run=trueparam returns the Marketo payloads as JSON without making any API calls.Test 1:
essentialconsent — UTMs should NOT be sentExpected:
non_essential_consent: false, andenriched_payload.input[0].leadFormFieldsshould have noutm_sourceorutm_mediumkeys.Test 2:
performanceconsent — UTMs SHOULD be sentExpected:
non_essential_consent: true, andenriched_payload.input[0].leadFormFieldsshould containutm_source: "google"andutm_medium: "cpc".Test 3:
functionalityconsent — UTMs SHOULD be sentExpected:
non_essential_consent: true, UTM fields present in enriched payload.Test 4:
allconsent — UTMs SHOULD be sentExpected:
non_essential_consent: true, UTM fields present in enriched payload.Test 5: No
_cookies_acceptedcookie — UTMs should NOT be sentExpected:
cookie_consent: "unset",non_essential_consent: false, no UTM fields in enriched payload.Summary table
_cookies_acceptedessentialunset/ missingfunctionalityperformanceallBefore merge
dry_runblock fromwebapp/views.pyIssue / Card
Fixes https://warthogs.atlassian.net/browse/WD-34696
Help
QA steps - Commit guidelines