Documentation · Cross-domain tracking

Cross-domain tracking on Admaxxer

When a visitor crosses from brand.com to app.brand.com (different root) or to checkout.shopify.com (different vendor), cookies and localStorage don't follow. Without help, they get a fresh visitor_id on the destination domain and all attribution dies. The Admaxxer pixel rewrites outbound links to a configured allow-list of domains, appending a signed _admx_v handoff token; the destination pixel reads the token, verifies the HMAC, restores the visitor_id, and scrubs the URL via history.replaceState. The result: a visitor who clicked a Meta ad on brand.com in March and lands on app.recharge.com in May still attributes to the original campaign — without any third-party cookie.

See the flow Configuration Comparison

The 4-step flow

  1. 1 Visitor lands on brand.com from a Meta ad

    The pixel mints a fresh visitor_id `V123`, stores it in localStorage, and stamps the first-touch UTM (`utm_source=facebook`, `utm_campaign=spring-launch-2026`) into the same key with a 365-day TTL.

  2. 2 Visitor clicks a link to app.recharge.com/portal

    Because `app.recharge.com` is in the merchant's `crossDomainHosts` allow-list, the pixel intercepts the click, computes the HMAC token over (visitor_id, current-minute), and rewrites the href to `app.recharge.com/portal?_admx_v=V123&_admx_s=S456&_admx_t=8a3c2f1b`.

  3. 3 On app.recharge.com, the pixel boots and reads `_admx_v`

    It re-computes the HMAC against its own copy of the merchant's secret (delivered via the pixel script-tag config or the dashboard-issued website settings) and compares to `_admx_t`. If valid AND the timestamp falls within the 6-minute TTL window, the pixel adopts `V123` as its own visitor_id instead of minting a new one.

  4. 4 Pixel scrubs the URL via history.replaceState

    The `_admx_v`, `_admx_s`, and `_admx_t` params are stripped from `location.search` before any analytics code runs. The visitor's existing first-touch UTM (Meta, from step 1) is preserved through the handoff because it lives in localStorage keyed under the workspace-issued website_id, not URL params.

What the rewritten link looks like

<!-- Original markup on brand.com: -->
<a href="https://app.recharge.com/portal">Manage subscription</a>

<!-- After pixel rewrite (transparent to your code): -->
<a href="https://app.recharge.com/portal?_admx_v=V123&_admx_s=S456&_admx_t=8a3c2f1b">
  Manage subscription
</a>

<!-- After arrival on app.recharge.com + history.replaceState: -->
https://app.recharge.com/portal

HMAC token format

The handoff token is an 8-character truncation of SHA-256(visitor_id + ':' + current-minute-epoch + ':' + website_secret). Eight characters of hex = 32 bits = roughly 4 billion possible tokens. Combined with the 6-minute rolling TTL window (current minute + previous 5), forging a single accepted token requires on the order of 1014 attempts per hour — far above any realistic threat for an analytics pixel.

The secret is the per-site signing secret that authenticates your pixel — one secret per website, rotated automatically whenever you reset your site's tracking credentials. The destination pixel fetches the same secret from your account's pixel configuration at boot time, so origin and destination always agree on the canonical value.

Why 8 chars and not 64?

URL-param real estate matters — some downstream platforms (Shopify checkout, paid-search redirect URLs) impose URL length caps. Eight chars + a 6-minute TTL is the security/usability sweet spot for an analytics handoff: long enough to defeat brute force at the rate analytics traffic can plausibly sustain, short enough that the URL stays readable.

Configuration

Enable cross-domain tracking from your dashboard: Site settings → Cross-domain tracking, then enter a comma-separated list of hostnames. The pixel only rewrites links whose hostname matches one of these entries — no wildcard auto-rewrite, no accidental leaking of visitor_id to unrelated domains.

<!-- Direct script-tag install: -->
<script
  defer
  src="https://admaxxer.com/script.js"
  data-website-id="admx_a1b2c3d4e5"
  data-cross-domain-hosts="app.brand.com,checkout.brand.com,app.recharge.com">
</script>

<!-- Or via the JS API after pixel boot: -->
<script>
  window.admaxxer = window.admaxxer || function(){(admaxxer.q=admaxxer.q||[]).push(arguments)};
  admaxxer('init', {
    websiteId: 'admx_a1b2c3d4e5',
    crossDomainHosts: ['app.brand.com', 'app.recharge.com'],
  });
</script>

Both forms are equivalent; the script-tag form is preferred when you can't run JS before the first link click. The JS API form is preferred when the host list is dynamic (e.g. multi-tenant SaaS where each customer's checkout domain differs).

What does NOT work

Shopify's Custom Pixel runs inside a sandboxed Web Worker (see Shopify Custom Pixel architecture). It has no DOM access, so it can't intercept arbitrary outbound link clicks — the URL-rewrite mechanism doesn't apply. For Shopify-checkout-specific cross-domain integrations (ReCharge, Bold Subscriptions, anything that redirects mid-checkout), use server-side stitching via customer_email_hash instead. Both Admaxxer's pixel and the merchant's destination platform emit a SHA-256 hash of the lowercased customer email; the revenue rollup joins on this hash to reconstitute the journey post-checkout.

Same for native mobile apps that open a web view (e.g. an iOS in-app browser): the pixel can rewrite links inside the parent page, but once Apple's SFSafariViewController or Android's Custom Tab takes over, JS context is lost. For those flows, propagate the visitor_id through your app's deep-link parameter scheme directly.

Comparison vs Datafast / Triple Whale

Datafast invented the URL-param cross-domain mechanism for DTC analytics pixels with _df_vid. Triple Whale doesn't have a documented cross-domain mechanism for arbitrary outbound links — their Sonar product solves the same problem via email-stitch (matching server-side conversions to pre-checkout sessions on email hash). Admaxxer matches Datafast on the URL-param mechanism and adds HMAC signing for security.

Cross-domain tracking: Admaxxer vs Datafast vs Triple Whale.
Capability Admaxxer Datafast Triple Whale
URL-param mechanism _admx_v + _admx_s + _admx_t (HMAC-signed) _df_vid (unsigned) Not documented — Sonar uses email-stitch instead
HMAC signing Yes — 8-char SHA-256 truncation over (visitor_id, current-minute), per-website secret No — raw visitor ID in URL N/A
TTL window 6 minutes (current minute + previous 5) No documented TTL N/A
URL scrub Yes — history.replaceState immediately after read Yes — history.replaceState N/A
Allow-list required Yes — explicit `crossDomainHosts` config; never auto-rewrites Yes — domain allow-list N/A

Frequently asked

Why HMAC sign the visitor_id at all?
Without a signature, anyone could append `?_admx_v=<their-id>` to a link and inject themselves into your funnel — or worse, replay another visitor's ID and corrupt their attribution. The 8-char SHA-256 truncation gives 32 bits of entropy, which combined with the 5-minute TTL window puts a brute-force forge attempt at roughly 10^14 attempts per hour. That's far above any realistic threat for an analytics pixel; the cost of forging a single accepted token isn't worth the marginal attribution shift.
What's the TTL window?
Tokens are valid for the current minute plus the previous 5 minutes — a rolling 6-minute window. This handles clock skew between origin and destination (HTTP requests in flight, intermediate redirects, browser preload) without leaving forged tokens valid for hours. If a visitor takes longer than 6 minutes between clicking the link and arriving at the destination (rare for normal navigation), they get a fresh visitor_id on the destination — same as if cross-domain tracking weren't enabled.
Does this work with the Shopify Custom Pixel?
Not directly. Shopify's Custom Pixel runs in a Web Worker sandbox with no DOM access, so it can't intercept arbitrary outbound link clicks. For Shopify-checkout-specific cross-domain (e.g. ReCharge, Bold Subscriptions, mid-checkout redirects), use server-side stitching via `customer_email_hash` instead — both surfaces emit the same SHA-256 email hash, and `the revenue rollup` joins on it.
Will users see the `_admx_v` param in their address bar?
Only for a fraction of a second on the destination page. The pixel calls `history.replaceState` immediately after reading and verifying the token, scrubbing `_admx_v`, `_admx_s`, and `_admx_t` from `location.search`. The user-facing URL is clean by the time the page is interactive. The original URL (with params) is never written to the browser's session history — back-button navigation lands on the scrubbed URL.
Are there privacy implications?
The handoff token carries the merchant's own visitor_id — a per-browser identifier scoped to their workspace. It's not a third-party cookie, doesn't carry email or PII, and can't be cross-correlated across merchants because each website's HMAC secret is unique. The destination domain must be in the merchant's explicit `crossDomainHosts` allow-list, so the pixel can't accidentally leak the visitor_id to an unrelated domain.
Can I disable cross-domain tracking on specific links?
Yes. Add `data-admx-no-rewrite` to any anchor element and the pixel skips it. Useful for affiliate links where you don't want your visitor_id to land in a partner's URL, or for `mailto:` / `tel:` links that the pixel already skips by default.
What about subdomain links — do those need this?
Usually no. Cookies and localStorage on `brand.com` are accessible from `app.brand.com` if the pixel is initialised with the eTLD+1 (`.brand.com`) as its scope. Cross-domain handoff is only needed when crossing root domains (`brand.com` → `recharge.com`) or vendor boundaries (`brand.com` → `checkout.shopify.com`). For pure subdomain navigation, the existing localStorage value just works.

Next steps