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 per-website, stored in pixel_websites.hmac_secret, and rotated whenever the merchant rotates their pixel website (re-issuing a new website_id). The destination pixel fetches the same secret via the workspace-scoped pixel-config endpoint 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; summary_kpis 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 `summary_kpis` 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