Stripe
Reads Checkout Sessions so each one can be matched to a visit.
- 01Create a restricted read-only key
- 02Paste it into Settings → Payments
- 03Wait for the backfill to finish
Setup guide
Three steps: paste a script, connect your payment processor, and confirm the first event lands.
Copy your site's snippet from the dashboard, then pick your platform below. No cookie banner needed: the script is cookieless by default.
This script also automatically captures campaign and click-ID parameters from the URL when a visitor arrives: Meta's fbclid, Google's gclid, Microsoft's msclkid, TikTok's ttclid, LinkedIn's li_fat_id, X's twclid, and standard UTM parameters. There's nothing separate to configure for this, and no Meta or Google account to connect.
Paste it before the closing </head> tag of your site's HTML.
<script defer src="https://thexly.com/script.js" data-website-id="YOUR-WEBSITE-ID"></script>Go to Settings → Integrations, and pick whichever platform your revenue runs through: Stripe, Lemon Squeezy, Polar, Dodo, or Yolfi. Stripe and Polar support scoped, read-only keys. Lemon Squeezy and Dodo only offer account-level keys, since neither has a permission-scoping option today. Either way, Thexly only ever reads payment data; the only write action any connection makes is registering its own webhook. Not using one of these five? See the Manual API instead.
Stripe's restricted-key permissions:
Visit your site, then check your Thexly dashboard. You should see your own visit appear within a few seconds. Make a test purchase, or wait for your first real one, to confirm revenue attribution is linking correctly.
The install script tracks pageviews automatically. Anything past that, a signup, a click on a pricing button, a form submission, you define yourself as a custom event. Custom events count toward your plan's event limit the same as pageviews. There are three ways to send one, in order of how much code they need.
1. Zero-code, with an HTML attribute
Add data-thexly-event to any link or button and Thexly tracks a click on it automatically, no JavaScript required. Add extra detail with data-thexly-event-* attributes.
<button data-thexly-event="signup-clicked" data-thexly-event-plan="agency">
Start free trial
</button>2. From JavaScript
Call thexly.track() from your own code for anything the attribute can't reach, form submissions, checkout completion, a step inside a multi-page flow.
thexly.track('signup-clicked', { plan: 'agency' });3. From your server
For events that happen server-side, a webhook firing, a subscription renewing, send them directly to the collector endpoint with your website ID.
curl https://your-thexly-domain/api/send \
-H "Content-Type: application/json" \
-d '{
"type": "event",
"payload": {
"website": "YOUR_WEBSITE_ID",
"name": "subscription-renewed",
"data": { "plan": "agency" }
}
}'When a visitor arrives from an ad, the script captures whatever click ID or UTM parameters are present in the URL. This happens automatically, with no ad account connection required. That information is attached to the visitor's session.
If that same visitor later completes a payment through Stripe, Thexly matches the payment back to that visitor, using the explicit session at checkout when one's available, or otherwise their most recent tracked session, so the payment is credited to the one channel that actually led to it, not to every platform that happened to serve an ad along the way. There's no expiration window on this: a session from months earlier is still eligible to match, as long as the visitor can still be identified.
Separately, you choose an attribution model per website: credit the channel that first brought the visitor in, or the one that closed the sale. You can change it anytime without losing history.
On the Agency plan, add each client as a separate website under your account. Each one gets its own tracking script and its own payment processor connection. Client data is kept separate, but all visible from the same login.
On the Agency plan, invite teammates from a website's Settings → Team tab. Add someone by their Thexly username and pick a role. If they don't have a Thexly account yet, they'll show as Pending until they sign up with that username.
Roles are per website, not account-wide, so someone can be a Member on one client and a Viewer on another. Remove access anytime from the same table.
Switching from Plausible or Umami? Upload an export file during setup and Thexly will bring that history in rather than starting from zero: daily traffic and page metrics from Plausible, or full sessions, page views, and events from Umami. GA4 import isn't available yet.
Reads Checkout Sessions so each one can be matched to a visit.
Same connection flow as Stripe, though key scoping varies by processor. Each connector's own guide covers exactly what to grant.
Not one of the five above? Record revenue yourself via a REST endpoint from your own backend.
Setup guides land with each connector.
Thexly doesn't set cookies and doesn't store your visitors' IP addresses. To tell one visitor's session from another without a cookie, the tracking script combines the visitor's IP, their browser's user agent, and your site, into a one-way hash. That hash rotates on a schedule (monthly by default), so it changes rather than identifying the same visitor indefinitely, and the raw IP that went into it is never written to the database.
No name, email, or other personal identifier is collected on the traffic and analytics side. That's what makes a cookie consent banner unnecessary for the tracking script itself.
Revenue data is different, by necessity: connecting Stripe or another payment processor pulls in whatever that processor already holds on a payment, customer email, name, amount, because reconciling a sale against a visit requires it. That data is exactly as sensitive as what your payment processor stores today, is read through an API key you control (scoped to read-only where your processor supports it) and can be disconnected at any time.
One caveat worth being explicit about: this describes what Thexly itself collects. Using Thexly doesn't automatically make your site, or a client's site, compliant with GDPR, CCPA, or any other regulation on its own, that depends on everything else the site does. Thexly removes the analytics-cookie problem; it isn't a substitute for a privacy policy or a full compliance review.
The script isn't sending any data
Open your browser's network tab and reload the page. If you see no request to the tracking endpoint at all, the most common causes are an ad blocker or privacy extension on your own testing browser (try an incognito window with extensions off), or a Content-Security-Policy header on your site that doesn't allow the script's domain. If you see the request but no data appears in the dashboard, double-check the website ID in the snippet matches the website you're viewing.
A payment isn't matching to a session
Every website has a Revenue diagnostics report built for exactly this: it shows each unmatched payment, why it couldn't be matched, and a "Copy for developer" button that copies the specific fix below straight to your clipboard. The three causes, and their fixes:
Track the checkout return
A plain Stripe Payment Link (a buy.stripe.com URL used as-is in an <a href>) gets the visitor's session attached automatically, no code needed. This fix only applies when checkout is created from your own server, a custom Stripe Checkout Session, Dodo, or LemonSqueezy call, since those never pass through the tracker's link decoration.
// Capture the visitor's current session before starting checkout
const sessionId = window.thexly.getSessionId();
// Send it along when you kick off checkout on your backend
fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ sessionId }),
});const session = await stripe.checkout.sessions.create({
client_reference_id: sessionId, // from the request above
success_url: 'https://yoursite.com/thank-you',
// ...line items, mode, etc.
});Identify the customer in the browser
For a returning customer paying through a saved card, subscription renewal, or any flow where no fresh checkout click happens, call thexly.identify() right after login or signup so a later payment can still be matched back to them.
// Right after login/signup, with whatever the payment provider
// will later report back on the payment (any one of these is enough)
thexly.identify({
email: user.email,
// or: stripeCustomerId: user.stripeCustomerId,
});Send checkout or customer context
If a payment arrives with no checkout id, session id, provider customer id, or email at all, common on a server-to-server integration with no browser step, there's nothing to match it against after the fact. Pass at least one of these through at checkout time.
curl https://your-thexly-domain/api/payments/manual \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"websiteId": "YOUR_WEBSITE_ID",
"transactionId": "the provider'"'"'s own payment/transaction id",
"sessionId": "the visitor session id, if you have one",
"email": "customer@example.com",
"amount": 49.00,
"currency": "USD"
}'My own visits are showing up in the numbers
On a self-hosted install, set the IGNORE_IP environment variable to your own IP address (or a CIDR range, for an office) and restart, and traffic from it will be excluded going forward. This isn't yet available as a per-website setting on Thexly Cloud.
My numbers don't match another analytics tool
Expect some difference, not a bug. Thexly doesn't use cookies, so it can't count a visitor the way a cookie-based tool does across long gaps or different browsers, and its attribution model (first-touch or last-touch, whichever you picked) may credit a sale to a different channel than a tool using a shorter lookback window. The Stripe revenue totals are the one number that should always reconcile exactly against your actual balance.
Need help? Email support. Most setup questions get answered within a day.