Set up website tracking

Chartsy's tracking script connects the traffic arriving at your site to the customers who end up paying - so you can see which channel brought each signup, which ones convert, and which ones produce customers who stay.

Three steps are required. Two more sections apply only if your site spans several domains or shows a cookie banner. Platform-specific instructions - Webflow, Squarespace, Wix, WordPress, GTM, Framer - are in Install by platform.

What you need first#

  1. A Chartsy account with your site added under Settings → Domains.
  2. Access to your site's HTML - a shared layout file, your CMS's header-injection setting, or Google Tag Manager.
  3. A connected payment provider (Stripe, Paddle, Freemius, Polar.sh, Creem.io or Lemon Squeezy) if you want revenue by source. You can install tracking first and connect billing later.

Tracking is disabled on localhost by default, so pointing a real key at your dev environment will not burn your monthly event allowance.

1. Add the tracking script (required)#

Copy your snippet from Growth Attribution in the Chartsy sidebar: step 1 of Set up tracking has it with your own site key:

<script async src="https://chartsy.fra1.digitaloceanspaces.com/production/static/track.js"
        data-key="ch_pk_your_key_here"
        data-api="https://dashboard.chartsy.app/api/attribution"
        data-cookies="on"></script>

It must render in <head> on every page. Where to put it:

Your setupWhere it goes
Framework with a shared root layoutapp/layout.tsx, layouts/default.vue, +layout.svelte or your base template - once
WordPress, Webflow, Squarespace, Wix, FramerThe site-wide custom code / header injection setting
Google Tag Manager already installedA Custom HTML tag firing on All Pages
Static multi-page HTMLBefore </head> on every page

Add it once per page load. Two copies double-count everything.

Script attributes

AttributeRequiredWhat it does
data-keyYesYour site's public key. Safe to expose - it is in your page source.
data-apiYesWhere events are sent. Use the value from your snippet.
data-allow-localhostNoSet to "true" to track on localhost while developing. Off by default.
data-cookiesYes"on" sets cookies; "off" or missing is cookieless. Must match your cookie mode - copy the snippet from Growth setup after changing it.

Check it worked

Load your site, then open Settings → Domains and click View setup checklist on the site's row. "Add tracking snippet" flips to done as soon as the first visit lands. That item is inferred from real traffic, so it is the only confirmation the snippet is actually firing rather than just pasted somewhere.

2. Tell Chartsy who signed up (required)#

Visits alone cannot be tied to revenue. Chartsy needs the email address at the moment someone creates an account. Pick whichever route matches your signup flow.

A real form

Add data-chartsy-signup to the <form>. The email field is picked up on submit - no JavaScript to write.

<form action="/signup" method="post" data-chartsy-signup>
  <input type="email" name="email" required>
  <button type="submit">Create account</button>
</form>

JavaScript or single-page apps

Call identify yourself, right after the signup succeeds:

window.chartsy.identify({ email: 'person@example.com' });

OAuth and social login

"Continue with Google" redirects the browser away and back, so there is no page left running to fire identify(). Handle it from your backend, right after the callback creates the user. The cookies the script already wrote survive the round trip:

// Node / Express - from your OAuth callback handler
const first = JSON.parse(decodeURIComponent(req.cookies.chartsy_attrib || '{}'));
const last  = JSON.parse(decodeURIComponent(req.cookies.chartsy_attrib_last || '{}')) || first;

await fetch('https://dashboard.chartsy.app/api/attribution/identify/', {
  method: 'POST',
  headers: {
    'Content-Type': 'text/plain',
    'Authorization': `Bearer ${process.env.CHARTSY_SECRET_KEY}`,
  },
  body: JSON.stringify({
    email: user.email,
    attribution_first: first,
    attribution_last: last,
    visitor_id: req.cookies.chartsy_visitor_id || '',
  }),
});

Keep the secret key server-side

Environment variable or secrets manager, never committed and never sent to the browser. Unlike the public key, it authenticates your backend.

Send the request even when the cookies are missing. A signup with no known source still counts; one that never arrives is invisible.

3. Connect revenue to sources (optional)#

With Stripe, Paddle, Freemius, Polar.sh, Creem.io or Lemon Squeezy connected and signups tracked, Chartsy matches signups to paying customers by email automatically - no payment code needed. That only breaks when someone pays with a different address than they signed up with - a personal signup and a company billing address, for example.

On Stripe, Paddle, Polar.sh, Creem.io and Lemon Squeezy, attaching the visitor id fixes it permanently:

// Stripe Checkout Session - Chartsy copies it onto the customer
metadata: { chartsy_visitor_id: req.cookies.chartsy_visitor_id }

// Paddle transaction or checkout
custom_data: { chartsy_visitor_id: req.cookies.chartsy_visitor_id }

// Polar checkout (polar.checkouts.create) - Polar copies it onto the order
metadata: { chartsy_visitor_id: req.cookies.chartsy_visitor_id }

// Creem checkout (POST /v1/checkouts)
metadata: { chartsy_visitor_id: req.cookies.chartsy_visitor_id }

// Lemon Squeezy checkout (createCheckout)
checkoutData: { custom: { chartsy_visitor_id: req.cookies.chartsy_visitor_id } }

On Stripe, Chartsy copies it from the Checkout Session onto the customer. A one-off mode: 'payment' checkout needs customer_creation: 'always' so there is a customer to hold it; with PaymentIntents or Elements, set it on the Customer instead - PaymentIntent metadata isn't read. Stripe Payment Links and Polar checkout links can't carry it - point their success page back at your site instead (Creem and Lemon Squeezy checkouts can use either), as the revenue attribution guide shows.

Revenue and churn then follow the visitor rather than the address, and keep working when the two diverge.

Freemius

Freemius Checkout has no custom-data option, so it can't carry the visitor id - Freemius payments are matched to visitors by email only. To make that reliable, lock logged-in users to their signup email where your site opens checkout, and record every buyer from purchaseCompleted so people who never signed up still get a source:

// Freemius Checkout
const checkout = new Checkout({
  product_id: '<your product id>',
  public_key: 'pk_...',
});
checkout.open({
  // Logged-in users pay with the email they signed up with
  user_email: currentUser?.email,
  readonly_user: Boolean(currentUser),
  // Records every buyer, including ones who never signed up
  purchaseCompleted: (data) => window.chartsy?.identify({ email: data.user.email }),
});

identify() never overwrites an existing signup, so a buyer who did sign up keeps that signup's source. With GDPR mode on, no customer emails are stored, so Freemius revenue can't be matched to a source. Connection details are in the Freemius guide.

If your site spans more than one domain#

Install the same snippet, with the same key, on every domain - and register each of them under Settings → Domains. Both halves matter: the script has to be present to measure anything, and the domain has to be registered for Chartsy to recognise it as yours rather than as an outside referral.

Subdomains - example.com and app.example.com - need nothing else. Registering the root covers every subdomain, and the two share a cookie automatically.

Separate root domains - example.com and exampleapp.io - cannot share a cookie at all; browsers keep them in separate jars. Chartsy handles the handover: when a visitor clicks from one of your registered domains to another, their identifier travels on the link as a _chid parameter, and it is removed from the address bar as soon as it is read. A signup on the second domain is attributed to the campaign that landed on the first, and the whole crossing counts as one visit rather than two.

It follows real links

The handover attaches to <a href> clicks. A button that navigates with window.location = … or a router push will not carry it; framework link components such as Next.js <Link> render real anchors and work fine. Pages viewed before crossing stay with the domain they were viewed on - attribution crosses, the page-by-page journey does not.

Cookie modes#

Set per site under Settings → Domains → Cookies & consent. What cookies add is cross-session identity - who came back, and what first brought them. The snippet carries the mode as data-cookies, so copy it again after switching.

ModeBehaviourCost
Use cookies (default)Full 90-day attribution for every visitor the script loads for. Consent is up to you.With a banner, visitors who decline are not counted.
CookielessNo device storage for anyone, so no banner is needed at all.Same-day attribution only - no first touch, no multi-day journeys.

Switching to Cookieless also clears cookies already on returning visitors' devices, the next time they load a page.

Excluding yourself#

Visit any tracked page once with ?chartsy_ignore=1:

https://yoursite.com/?chartsy_ignore=1

That browser is never tracked again - no cookies, no events, nothing counted against your plan. Use ?chartsy_ignore=0 to undo it. Do this on each browser and device you use to check your own site. Traffic from private and internal IP addresses is excluded automatically.

Troubleshooting#

Every signup says "Unknown source"

Almost always the script is missing from the page where visitors first arrive. If your marketing site and app are on different subdomains, the script has to be on both - the crossing between them is internal navigation, so if the marketing side never recorded the original visit, there is nothing left to attribute. If you load the script after your cookie banner, visitors who never accept aren't tracked at all - choose Cookieless to count everyone.

No data at all

Check, in order:

  • The domain is registered under Settings → Domains
  • The script renders in <head> on the page you are testing
  • You are not on localhost without data-allow-localhost="true"
  • You have not opted this browser out
  • No ad blocker or content blocker is active

It worked, then stopped after a site change

Most often a JavaScript optimisation feature - "combine scripts", "defer JavaScript", "minify and inline" - in a caching or speed plugin, or a build step that rewrites script tags. Chartsy reads its settings off its own <script> tag, so anything that strips its attributes or inlines it without them leaves it running with no key, measuring nothing. Exclude track.js from combining, deferring and inlining.

Visits but no signups

Step 2 is not wired, or your signup goes through OAuth and needs the server-side call.

Signups but no revenue

Link a payment account to the site under Settings → Domains, and check step 3 - without the metadata, matching falls back to email, which fails when billing and signup addresses differ. On Freemius, which matches by email only, lock checkout to the signup email and identify buyers on purchaseCompleted, and check GDPR mode is off.

Fewer visits than another analytics tool

Expected. Chartsy counts one visit per session per source, not one per pageview, and excludes bots, internal IPs and opted-out browsers.

What gets collected#

Page URLs and referrers, UTM parameters, the landing page, and the last few pages of the session that converted. Country, derived from the IP address. A visitor identifier - either a first-party cookie or, in cookieless mode, a server-side hash that rotates every 24 hours. At signup, the email address your own code passes.

No biometric data. No third-party cookies. No ad networks. IP addresses are not stored on any attribution record - they are used in flight to resolve a country, limit abuse and compute the identifier, then dropped. The identifier is scoped per site, so the same person visiting two different Chartsy customers is two unrelated records.

Related articles

Chartsy Team

Written by

Chartsy Team

Analytics team at Chartsy

The Chartsy Team writes guides, product updates, and resources to help SaaS and eCommerce founders make sense of their metrics, without SQL or spreadsheets.

Chartsy
Ministry of Economy and Innovation
Startup Albania

The Chartsy program is realized with the financial support of the Albanian Government through the Ministry of Economy and Innovation, under the Grant 2026 scheme, and is implemented by the Innovation4Albania Agency.