Repository navigation
Conversation
Booking/form widgets that the site's own script loads in an iframe never pass through link decoration, and rewriting an iframe src after insertion loads it twice. window.ClickTrail.decorateEmbedUrl(url) returns the URL to load instead, so the attribution is in the iframe URL from its first request. - Adds last-touch utm_* and one click ID (the D3 selection; wbraid/gbraid and ScCid under their own names) for https hosts in the new "Embedded widget hosts" setting (embed_allowed_domains, filter clicutcl_embed_allowed_domains, empty by default, exact host match). - Returns the input unchanged without marketing consent, for other hosts, non-https or signed URLs (link_skip_signed), or with no attribution. - Never overwrites site params; skips stored UTMs when the URL already has any utm_*, so one URL never mixes two campaigns. - Decorator.oneClickId now backs both this and the Calendly stamp. - Docs: INTEGRATIONS, HOOKS-REFERENCE, SECURITY-PRIVACY, SETTINGS-AND-ADMIN, readmes, changelog. Smoke checks in tools/qa/smoke.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5d4d73b949
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const bad = ["x-amz-signature", "signature", "sig", "token"]; | ||
| return bad.some(k => qs.has(k) || qs.has(k.toUpperCase())); |
There was a problem hiding this comment.
Detect mixed-case signature parameters
When an allowlisted embed uses a conventionally cased signature such as X-Amz-Signature or Signature, URLSearchParams' case-sensitive lookup misses it because only the lowercase and fully uppercase spellings are checked. decorateEmbedUrl() then appends attribution and reserializes the URL despite the default signed-URL protection, invalidating the signature and potentially preventing the iframe from loading; compare parameter names case-insensitively instead.
Useful? React with 👍 / 👎.
| const order = data.attribution_selected_click_id | ||
| ? (selected ? [selected] : []) | ||
| : ['gclid', 'gbraid', 'wbraid', 'fbclid', 'msclkid', 'ttclid', 'li_fat_id', 'twclid', 'dclid']; |
There was a problem hiding this comment.
Preserve legacy Snapchat and Pinterest click IDs
For returning visitors whose attribution was stored before attribution_selected_click_id existed, the fallback omits the already-supported sccid and epik keys. After an upgrade, an embed handoff for a visitor acquired through Snapchat or Pinterest therefore sends its UTMs but no click ID until a new click is captured, even though both IDs are in D3_CLICK_ID_KEYS and the new API supports them; include them in the legacy fallback order.
Useful? React with 👍 / 👎.
| const click = this.oneClickId(data); | ||
| if (click) add(click[0] === 'sccid' ? 'ScCid' : click[0], click[1]); // Snapchat's own param name, as link decoration sends it |
There was a problem hiding this comment.
Avoid adding a second click ID to embed URLs
When the source widget URL already contains a click ID from another platform—for example gclid=OLD while the stored selection is fbclid=NEW—this adds the selected ID without checking the other supported click-ID keys, so the vendor receives two conflicting IDs despite the documented one-click-ID contract. Treat any existing supported click-ID parameter like the existing all-or-nothing UTM guard, while retaining the site's parameter unchanged.
Useful? React with 👍 / 👎.
Acceptance: for an https host in "Embedded widget hosts",
window.ClickTrail.decorateEmbedUrl(url)returns the URL with last-touchutm_*and the one selected click ID; for anything else (other host,www./sibling variant, http, signed URL, no consent, no data, non-string) it returns the input unchanged.Why
Booking/form widgets that the site's own script loads in an iframe (e.g. a popup doing
iframe.src = url; container.appendChild(iframe)) never go through link decoration, and rewritingsrcafter insertion loads the iframe twice. Giving the site a function to call where it already setssrcputs the attribution in the iframe URL from the first request, with no dependency on pageloador a vendor "ready" message. Motivated by a client booking-widget QA where the handoff was lost on fast clicks.What
Decorator.decorateEmbedUrl+ publicwindow.ClickTrail.decorateEmbedUrl(url).utm_*(all-or-nothing: skipped if the URL already has anyutm_*) + one click ID fromDecorator.oneClickId(D3 selection,wbraid/gbraidown keys, Snapchat asScCid). No visitor/session ID, history or token.embed_allowed_domains("Embedded widget hosts", empty by default, sanitized bysanitize_domains_csv) and filterclicutcl_embed_allowed_domains(filter values normalized the same way). Exact host match: a third-party widget on a sibling subdomain must be listed.link_skip_signedis on (default).calendlyStampnow uses the sharedoneClickId(same behavior).Not in this PR (deferred, with triggers)
srcrewriting (MutationObserver): only if a site cannot edit its embed script and a double-load spike passes.Verification
node tools/qa/smoke.js: newvalidateEmbedUrlDecoration(selected click only, last-touch UTMs, site params kept, no UTM mixing, other host /www./ sibling / http / signed /javascript:/ non-string unchanged,wbraid,ScCid, no consent, empty allowlist). Mutation checks (consent gate removed, suffix host match, wrong click ID) each fail the suite.cmp-transition,consent-bridge,repeat-visit-scenariospass.applyTouchmergeslt_*per field, so a newer touch withoututm_termkeeps the older term. That affects every outbound path (links, forms, events) and should be its own fix.🤖 Generated with Claude Code