Cross-Domain Tracking for Onchain Apps article thumbnail

Cross-Domain Tracking for Onchain Apps

Yos Riady

Yos Riady

ยท Published

ยท Updated on

Updated

Updated

Key takeaways

  • To keep UTMs and referrers from site to app, put your site, app, docs, and blog on subdomains of one root domain if you can. Formo shares its visitor cookie across subdomains by default.

  • Formo strips same-domain referrers, so a click from example.xyz to app.example.xyz reads as internal navigation, not as a referral.

  • Forward UTM and referral parameters on the links from your marketing site into the app. Browser session storage does not cross subdomains.

  • Wallet in-app browsers and separate root domains get their own visitor ID. The wallet address joins them after the wallet connects, but not before.

  • Embedded widgets in iframes on partner sites lose cookies. Use referral codes in the widget URL and onchain builder codes instead.

Cross-domain tracking keeps your UTMs and referrers attached to one visitor from your marketing site to your app. It also covers your docs, partner widgets, wallet browsers, and mobile apps. Subdomains of one root domain can share a first-party cookie, so they are the easy case: forward your campaign parameters on every link into the app. Separate domains, iframes, and wallet in-app browsers break the link. There, the connected wallet address is often the only key that can join the journey again.

What is cross-domain tracking for onchain apps?

Cross-domain tracking for onchain apps is the practice of keeping one visitor identity and one traffic source across every domain a user touches before and after a wallet connect. It covers subdomains (site, app, docs), separate domains, embedded widgets, in-app browsers, and mobile deep links. The goal: credit each swap to the campaign that started the journey.

Most crypto products split across hosts: a site builder for marketing, a framework for the app, a docs platform, and partner widgets. Each split is a place where a cookie, a referrer, or a UTM tag can disappear.

What does a typical onchain user journey look like?

A typical onchain user journey crosses three to five hosts before the first transaction. A user sees a post on X, lands on the marketing site, reads the docs, opens the app, connects a wallet, and signs a transaction in a browser extension wallet or a wallet's in-app browser. Each hop has a different risk of losing identity or source.

Hop

Example

What can break

Social link to site

X or Telegram post to example.xyz

In-app browsers often send no referrer

Site to app

example.xyz to app.example.xyz

Cookie scope; UTMs not forwarded

Docs and blog

docs.example.xyz, blog.example.xyz

Hosted on a different platform without the SDK

Partner widget

Swap or bridge widget on partner.com

Iframe storage is third-party

Aggregator or wallet browser

Dapp opened inside a wallet or aggregator app

Separate cookie jar; new visitor ID

Mobile deep link

Link opens a wallet app at your dapp URL

Query string dropped by the link format

Wallet connect

User connects on app.example.xyz

None. This is where identity becomes durable

Why does tracking break between domains?

Tracking breaks between domains because browsers isolate storage by site. A cookie set on one root domain cannot be read on another. Session storage is per origin, so it does not even cross subdomains. Referrer policies and in-app browsers trim or drop the referrer. Iframes on other sites get partitioned or blocked storage.

The main causes:

  • Cookie scope. A cookie set for app.example.xyz only is invisible on example.xyz. A cookie set for .example.xyz works on both. No cookie crosses to examplefi.com. See first-party cookie and third-party cookie.

  • Session storage. It is scoped to one origin and one tab (see MDN on sessionStorage). Data kept there on example.xyz is not there on app.example.xyz.

  • Referrer loss. A strict Referrer-Policy, an HTTPS to HTTP downgrade, or an in-app browser can remove the referrer.

  • In-app browsers. X, Telegram, and wallet apps open links in their own browser with their own storage. The same person gets a new visitor ID there.

  • Iframes. A widget from your domain inside partner.com runs in a third-party context. Many browsers block or partition its storage (see MDN on state partitioning).

How does Formo track visitors across subdomains?

Formo tracks visitors across subdomains with a first-party cookie set on the root domain by default. The visitor ID is shared between example.xyz, app.example.xyz, docs.example.xyz, and any other subdomain, as long as each one runs the SDK in the same Formo project. You do not need extra code for this.

What the docs specify:

  • Root-domain cookies by default. The crossSubdomainCookies option is true by default and sets identity cookies on the root domain, such as .example.com. Set it to false to scope cookies to the current host.

  • Migration. The SDK moves existing host-scoped cookies to the root domain, so visitors are not double counted.

  • One project, one write key. Formo recommends the SDK on both the website and the app, in the same project with the same write key.

  • Internal referrers removed. Formo strips same-domain referrers, such as blog.example.com to app.example.com, before channel classification, so internal navigation rolls up to Direct instead of Referrers.

  • First-party only. Formo uses first-party cookies and sets no third-party or cross-site tracking cookies.

  • Per-host reports. The Origins breakdown groups traffic by protocol and domain, which helps when the app spans many subdomains.

One detail matters for campaign data. Inside one tab on one site, the SDK keeps the landing URL's UTM parameters, click IDs, and referral code, plus the first external referrer, and adds them to later events whose URL has none. A new UTM or click ID on a later URL replaces the stored value. That memory uses browser session storage, which does not cross to another subdomain. So forward the campaign parameters on your "Launch app" link.

Formo's docs describe subdomain sharing only. They do not describe a visitor ID handoff between two separate root domains, so treat example.xyz and examplefi.com as two visitors until the wallet connects.

How can the wallet address join journeys across domains?

The wallet address joins journeys across domains because it is the same on every host, browser, and device. Once a user connects on any of your properties, events that carry that address belong to one wallet profile. The address is a strong join key after connect. It cannot recover anything a visitor did before connecting on that browser.

How it works in Formo:

  • Connect and identify. The SDK captures the connect. Call identify after a wallet connect or sign-in, and pass your user ID if you have accounts.

  • Linking. Formo links wallets when they share the anonymous ID cookie (the same browser, across sessions and days) or the same identify user ID. Linked Addresses on a profile lists wallets seen on the same browser; Clusters also join wallets through a shared user ID. Formo does not use IP addresses, fingerprinting, or onchain transfers to link wallets.

  • Full-history attribution. A wallet profile's first touch and last touch use the wallet's full history. Each field is resolved on its own.

  • Onchain events. A contract event inherits the UTM parameters, referrer, and country of the same wallet's SDK session from the last hour. With no SDK session in that hour, the event is Direct.

The limits are real:

  • Pre-connect touches stay anonymous. Wallet attribution reads events that carry the address. A visit on another browser that never connected stays on its own anonymous profile.

  • One person, many wallets. A user may connect a hot wallet on desktop and a different wallet on mobile. Only a shared cookie or user ID links them.

  • Shared wallets. A Safe multisig or a team-run EOA (externally owned account) puts several people behind one address.

  • Privacy. A wallet address is public and links to a full onchain history. Treat it with the same care as other personal data.

See identity resolution and wallet clustering.

How do you keep attribution intact across every hop?

You keep attribution intact by sharing one visitor ID where the browser allows it, carrying campaign parameters where it does not, and identifying the wallet as early as the product allows. The steps below cover the site-to-app path first, then social links, widgets, and mobile.

  1. Install the SDK on every host. Put it on the site, app, docs, and blog, in one Formo project with one write key.

  2. Keep cross-subdomain cookies on. Leave crossSubdomainCookies at its default of true.

  3. Forward campaign parameters into the app. Copy utm_* and ref from the landing URL onto every link to app.example.xyz, so the app's first page view carries them.

  4. Set a referrer policy. Formo recommends no-referrer-when-downgrade, set as a response header or a meta tag.

  5. Tag every social link. Put UTM parameters on X, Telegram, Discord, and Farcaster links, because in-app browsers often drop the referrer. For paid ads, Formo also uses click IDs for the channel when the referrer is missing.

  6. Identify on connect. Call identify with the address, and your user ID if users log in.

  7. Give partners a referral code. Formo detects ref, referral, refcode, af, and referrer by default, and you can add your own parameter names or a path pattern.

  8. Add builder codes for embedded flows. If your widget appends an ERC-8021 builder code to transactions, Formo parses it from calldata with no setup.

What about widgets, in-app browsers, and mobile wallets?

Widgets, in-app browsers, and mobile wallets need separate fixes, because each runs in its own storage context. You cannot share a cookie into them. Instead, carry the source in the URL or in the transaction itself, and let the wallet connect join the identity later. The table shows what works today.

Scenario

What breaks

General approach

Formo support

Swap or bridge widget in an iframe on partner.com

Cookies and storage partitioned or blocked

Put the partner ID in the widget URL; tag transactions

Referral parameters and builder code parsing

Dapp opened in a wallet's in-app browser

New visitor ID; referrer often missing

Keep UTMs in the dapp URL inside the deep link

UTMs and referral codes read from the page URL

Aggregator sends users to your app

Referrer may be the aggregator or empty

Ask the aggregator for a ref or UTM on outbound links

Referral and UTM capture

Link from X or Telegram

Referrer dropped by in-app browser

UTM-tag every link

UTM capture; click IDs for paid ads

Deep link into your React Native app

No browser cookie at all

Put UTMs and ref on the deep link

Mobile SDK reads UTMs and referral codes from deep links

Web to app install

Install breaks the link

Use the store's referrer where one exists

Android Play Install Referrer supported; iOS not supported in the SDK

Test each wallet's deep link before a campaign. A wrong format can drop the query string, and the UTMs with it.

Frequently asked questions

Does Formo track users across subdomains by default?

Yes. The crossSubdomainCookies option is on by default and sets the visitor cookie on the root domain, so example.xyz, app.example.xyz, and docs.example.xyz share one visitor ID. Install the SDK on each subdomain in the same project with the same write key.

Why does my app show Direct traffic after users click from my website?

Formo strips same-domain referrers, so a click from example.xyz to app.example.xyz counts as internal navigation and reads as Direct. The original source sits on the landing page view. Forward UTM parameters on your app links to keep the source on the app's first page view as well.

Can Formo link visitors across two different root domains?

Not by cookie. Formo's documented sharing covers subdomains of one root domain. Across separate root domains, each domain has its own visitor ID. When the same wallet connects on both, events that carry that address land on one wallet profile.

Do UTM parameters survive a wallet's in-app browser?

Only if the URL that opens in the wallet still contains them. The in-app browser has its own storage, so earlier visits in another browser do not carry over. Put UTMs in the dapp URL inside the deep link and test the link before launch.

Is the wallet address a good cross-domain join key?

It is the best key after connect, because it is the same everywhere. It does not cover visits before the connect, it can map one person to many wallets, and it can map one wallet to a team. Pair it with an identify user ID when users have accounts.

How do I attribute swaps from a widget on a partner site?

Give each partner a referral code in the widget URL, and append an ERC-8021 builder code to the widget's transactions. Formo detects referral parameters in the page URL and parses builder codes from transaction calldata.

Related articles

About the Author

About the Author
About the Author
Yos Riady

Founder

Founder

Yos is the founder of Formo, where he helps DeFi teams make analytics and attribution simple. Prior to Formo, Yos was a staff software engineer and tech lead at Chainlink Labs. He helped scale Chainlink into the industry-standard oracle for leading DeFi protocols. A long-time builder in crypto with experience across smart contracts, data engineering, and security.

Yos is the founder of Formo, where he helps DeFi teams make analytics and attribution simple. Prior to Formo, Yos was a staff software engineer and tech lead at Chainlink Labs. He helped scale Chainlink into the industry-standard oracle for leading DeFi protocols. A long-time builder in crypto with experience across smart contracts, data engineering, and security.

Table of Contents

Measure what matters onchain

Formo makes analytics and attribution simple for DeFi apps.

Measure what matters onchain

Formo makes analytics and attribution simple for DeFi apps.

Measure what matters onchain

Formo makes analytics and attribution simple for DeFi apps.

Stay up to date with weekly product updates and the latest industry insights.

Stay up to date with weekly product updates and the latest industry insights.

Stay up to date with weekly product updates and the latest industry insights.