Skip to content

Keep the arrival UTM when visitors click the site's own UTM links - #91

Merged
rockwellll merged 8 commits into
mainfrom
utm-in-site-navigation
Oct 24, 2026
Merged

rockwellll merged 8 commits into
mainfrom
utm-in-site-navigation

Conversation

@rockwellll

@rockwellll rockwellll commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Stores tag their own on-site links with UTMs. does it on every home-page banner and product popup, for example /electrohogar?utm_source=banner_a_electrohogar_12.10&utm_medium=…&utm_campaign=….

The SDK treated every UTM in the URL as a new click. new UTM() runs for every Page: once at initialize, and again on every track that passes a url. Each run overwrote hello_utm with a fresh observed_at. Two things went wrong:

  • The store's own link replaced the click that brought the visitor. A shopper who arrived from a Hellotext card or an ad and then clicked the store's popup was attributed to the popup. Its click time was also later than the real arrival.
  • Single-page stores re-stamped the same UTM on every tracked event (VTEX IO), so the arrival kept looking later than it was.

Change

src/models/utm.js now sorts each UTM read from the URL into one of three cases, without depending on store naming:

  • Arrival: on the URL the document was loaded with (from the navigation timing entry, which keeps the original address on single-page stores), and not handed over from another page of the same site with different UTMs. Same site is compared by root domain via Page.getRootDomain; a same-site redirect that keeps the query is still the arrival. Stored in hello_utm, fires utm-set as before.
  • In-site: any other UTM. A single-page navigation to a link with UTMs, or a reload from another page of the site. It doesn't replace the arrival and doesn't fire utm-set. It's stored in hello_utm_internal and sent with events as internal_utm_params (Page.trackingData), so the server can recognise the same UTM when a commerce platform reports it on the order. A new arrival clears it.
  • Hellotext's own UTMs (utm_source=hellotext) always count as an arrival, even when opened inside the store, for example from the webchat.

observed_at is kept when the same UTM is read again: in the same document (the time is at or after performance.timeOrigin), on a reload, or on a back/forward navigation. A new navigation to the same link stamps a new time. save() (the webchat path) keeps stamping now.

Without navigation timing, the current address is treated as the one the document was loaded with, which is the old behavior.

Rollout

The VTEX pixel loads cdn.jsdelivr.net/npm/@hellotext/hellotext/dist/hellotext.min.js unpinned, so a release reaches VTEX stores without an app release. Purge jsDelivr after publishing. The Rails API passes event params through as-is, so internal_utm_params is ignored until the server reads it.

Tests

  • __tests__/models/utm_navigation_test.js (new, 18 cases):
    • arrivals from another site, from no referrer, and through a same-site redirect;
    • in-site UTMs on single-page and reloading stores, subdomains, and after an ad arrival;
    • Hellotext links opened inside the store;
    • keeping observed_at in the same document, on reload and on back/forward, and stamping it on a new click;
    • clearing the in-site UTM on a new arrival;
    • no navigation timing.
  • page_test: internal_utm_params is included only when present.
  • Full suite: 42 suites, 837 tests pass.

🤖 Generated with Claude Code

Stores tag their own popups, banners and menu links with UTMs. Every page or
tracked event re-read the URL and overwrote hello_utm with a fresh observed_at,
so a click on the store's own popup replaced the click that brought the
visitor, and single-page stores re-stamped the same UTM on every event.

A UTM now counts as an arrival only when it is on the URL the document was
loaded with and the visitor did not come from another page of the same site.
In-site UTMs go to hello_utm_internal and are sent as internal_utm_params.
Hellotext's own UTMs always count as an arrival. Re-reading the same UTM in
the same document, on a reload or on a back navigation keeps observed_at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2762d0c6ed

ℹ️ 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".

Comment thread src/models/utm.js Outdated
Comment thread src/models/utm.js Outdated
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T18:32:37.868720Z f6b6a18 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

rockwellll and others added 2 commits October 24, 2026 15:15
On a reloading store, a page reached from the store's own tagged link often
keeps the same UTMs in its links (pagination, filters). The referrer then
carries the same UTMs, which was read as a redirect of the arrival: the store's
link overwrote hello_utm and the in-site marker was deleted. Same-site pages
whose UTMs equal the stored in-site ones now stay in-site. Likewise, an
arrival whose UTMs the store keeps on the next page is the same click, so its
observed_at is no longer re-stamped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Hellotext link opened from an untagged page of the store compared only UTM
values, so a campaign stored on an earlier visit kept its old observed_at and
fired no utm-set on a genuine new click, leaving integrations such as the VTEX
orderForm stamp with the old click. Hellotext clicks now keep their time only
when read again during the same document or on a reload or back navigation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bfee56075

ℹ️ 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".

Comment thread src/models/utm.js Outdated
Only outside arrivals cleared hello_utm_internal. A Hellotext link opened in
the store and the webchat's explicit save() recorded a new click but kept the
previous store banner, which every later event kept sending as
internal_utm_params. All three paths now record the click through one helper
that clears the in-site marker once the new click is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69a5201058

ℹ️ 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".

Comment thread src/models/utm.js
Reloading a single-page store on the store's own tagged link loads the
document with the link's UTMs while the referrer is still the one the visit
started with, so the link was taken as a new arrival: it overwrote the real
acquisition and cleared hello_utm_internal. A reload or a back or forward
navigation that restores the stored in-site UTMs now keeps them in-site; a new
navigation from outside the site with the same UTMs is still an arrival.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 430d7c12aa

ℹ️ 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".

Comment thread src/models/utm.js Outdated
The navigation type describes the whole document, so on a single-page store it
stays "reload" for links opened after reloading an untagged page. A Hellotext
link opened in-page then kept an earlier visit's observed_at and fired no
utm-set. The reload and back/forward rule now applies only when the UTMs were
on the URL the document was loaded with; same-document re-reads still keep
their time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24e9cf092e

ℹ️ 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".

Comment thread src/models/utm.js Outdated
Writing hello_utm fires utm-set synchronously, and the in-site cookie was only
deleted afterwards, so a utm-set listener that tracked an event sent the old
banner as internal_utm_params with the new click. A new click is now validated
first, then the in-site UTM is cleared, then the click is written; an
incomplete click still clears nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f12360889

ℹ️ 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".

Comment thread src/models/utm.js Outdated
Only the latest in-site UTM is stored, so reloading or going back to an
earlier store link after opening another one promoted it to an arrival,
overwriting the acquisition and clearing the in-site cookie. A reload or back
or forward navigation now never records a new click: restored UTMs other than
the stored arrival are in-site, a restored Hellotext link does not replace a
newer arrival, and with no arrival stored the restored UTMs are the arrival.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rockwellll
rockwellll merged commit dd6975f into main Oct 24, 2026
3 checks passed
@rockwellll
rockwellll deleted the utm-in-site-navigation branch October 24, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant