How you work with the user
Pasting this file is a request to set up Trfyx, not permission to change whatever you like. Work in this rhythm:- Installing the tracking script needs no extra permission. It’s the one thing this file asks you to do. Ask for the website ID and domain if you don’t have them, then add the script (step 2).
- For anything else, suggest first and let the user choose. That covers custom events, identify, properties, revenue attribution and HTML attributes. Read the codebase and present one short list of what you could add (step 3). Implement only what the user picks.
- Once the user has picked, do the work without stopping at every step.
Implement all the chosen items, then summarise what you changed. Check
back mid-way only if you need something you don’t have, or the work turns
out bigger or different from what you described. For example, if the
checkout code needs restructuring, or
client_reference_idis already used for something else. - Keep changes to payment and sign-in code minimal and additive. Add the tracking lines; never change prices, amounts, currencies, redirect URLs or existing logic.
- Never do these unless the user asks: commit, push, deploy, install
packages, edit
.envfiles or secrets, or delete existing code. - Don’t invent values. Never guess a website ID or domain. If the code doesn’t match what this file describes, say so instead of improvising.
- Respect privacy. Never send passwords, payment details or text the
user typed into forms. Name and email go only in
identifyor the revenue email fallback.
Workflow
Step 1: look before you touch anything (read-only)
Find out, without editing:- The framework and where the root layout /
<head>lives. - Whether Trfyx is already installed: search for
script.jsorscript.cookieless.jswithdata-website-id, and forwindow.trfyxortrfyx(. - Where sign-up, sign-in, onboarding, key product actions, lead forms and checkout happen. Note the line where each one succeeds.
- Which payment provider is used (Stripe, LemonSqueezy, other) and where the checkout session, link or payment is created on the server.
- Where the signed-in user object is available on the client.
Step 2: install the script
If the script is missing, ask the user for (unless they already gave them):- their website ID, from Trfyx → Settings → Installation → Tracking
script (the
data-website-idin the snippet), and - the domain they registered in Trfyx.
<head>:
- Both
data-website-idanddata-domainare required; without either the script does nothing. data-domainis the root domain (example.com, notwww.example.com). The visitor cookie is set on it, sowww.,app.andblog.share one visitor.- If the user’s Trfyx install screen shows a different
src(a custom tracking subdomain, orscript.cookieless.js), use what their screen shows.
For Google Tag Manager or another tag manager, use a Custom HTML tag that
injects the script:
localhost, 127.0.0.1, *.local, *.localhost and
file:// so development traffic doesn’t pollute real data. To test locally
the user can temporarily add data-allow-localhost="true". Don’t leave it in
committed code.
After installing, tell the user what now works with no further code (see
“What the script does on its own”), then go straight on to step 3.
Step 3: suggest what else to add
Using what you found in step 1, give the user one numbered list of what you could add, each with the file and one line on why. For example:I can add:Wait for the answer. Implement only what the user picks.Which of these would you like? (“all” is fine.)
signup_completedevent after sign-up succeeds (src/auth/signup.tsx:88): counts real sign-ups for a sign-up funnel.identifyin the signed-in layout (app/(app)/layout.tsx): shows signed-in users by name in the Users report.- Revenue attribution in the Stripe checkout route (
app/api/checkout/route.ts:21): ties each payment to the visitor’s traffic source.data-trfyx-goalon the “Start free trial” buttons (components/hero.tsx): counts CTA clicks.
Step 4: implement the chosen items
Follow the sections below. Do all the chosen items in one go, then list the files and lines you changed.Step 5: verify, then tell the user what to set up in Trfyx
See “Verify it works” and “Goals and funnels”. Code alone creates no goals.What the script does on its own
Once installed, with no other code:
Session replay, heatmaps and web vitals are switched on per site in the Trfyx
dashboard; no code is needed. Inputs are masked in replays by default.
The script ignores automated browsers (Playwright, Puppeteer, Selenium,
headless Chrome) and pages shown inside an iframe.
Custom events
- The first argument is the event name. There is no
"event"command:trfyx("event", "signup")is wrong. - The second argument is optional properties (see “Properties”).
- The optional third argument is a callback that runs when the request
finishes, e.g. to navigate away only after the event was sent:
window.trfyx("download_clicked", {}, () => { location.href = url; }). - Use short
snake_casenames and keep them stable. Goals match the name exactly and case-sensitively. - Reserved names, never use them as events:
payment,identify,props(these are commands), andpageview,heartbeat,engagement(these can’t be goals). Don’t reuse the automatic namesform_submission,file_downloadandexternal_linkfor something else.
Always queue calls (important)
The script loads withdefer, so window.trfyx may not exist when your code
runs. Calling it then throws. Add this queue stub once; calls made before the
script loads are replayed when it loads.
Plain HTML, placed before any call (e.g. right above the Trfyx script tag):
lib/trfyx.ts) and call
it everywhere instead of window.trfyx:
Send conversions on success, not on click (important)
A click or a form submit also happens when the request then fails (email already taken, validation error, card declined). Send the event after the server confirms success:- send the event on the page the user lands on afterwards (
/welcome,/verified), once per success; or - skip the event and use a pageview goal on that page (no code at all).
Events worth suggesting
Properties
Properties let reports and goals split an event, e.g. sign-ups bymethod.
The browser checks properties before sending, and one bad property drops
the whole event. The only sign is an error in the browser console. So:
- Names: letters, digits,
_and-only, 1–32 characters. Names are lowercased (planTypebecomesplantype), so writesnake_case. - At most 10 properties per call. An 11th drops the event.
- Values become strings, are cut to 255 characters, and have
< > ' " &,javascript:,data:andvbscript:removed. Send flat strings, numbers or booleans. Nested objects and arrays are not stored. - Don’t put personal data (email, phone, full name, address, free text the user typed) in event properties.
Properties on every event: props
Identify signed-in users
- Without
user_idthe call is ignored. - The visitor then appears by name in Trfyx’s Users report with all their sessions and events on that browser, including those from before they signed in.
- Call it on every page load while the user is signed in (e.g. in the authenticated layout), and right after sign-up or sign-in succeeds.
- Updating a user (new name, new plan): call
identifyagain with the sameuser_idand the new values. The latest value of each trait wins. - Send flat values only, and keep it to about 25 traits.
- Only identify with real data from your session or database, never from unconfirmed form input.
- Nothing is needed on sign-out.
- When you suggest identify, mention that it sends
nameandemail, so the user can choose to send onlyuser_idand a plan instead.
<TrfyxIdentify user={…} /> once in the signed-in layout.
Revenue attribution
Trfyx ties each payment to the visitor who made it, and so to their source, campaign, landing page and country. It takes two parts. Part 1 (the user, in Trfyx): connect the payment provider under Integrations → Stripe or LemonSqueezy. Trfyx creates the webhook itself. You don’t write webhook code for Trfyx. Part 2 (code, if the user picked it): pass the visitor’s ID into the checkout. Without it a payment can only be matched by weaker signals, or not at all.Where the visitor ID comes from
The script stores it in a first-party cookie nameddf_visitor_id, set on
the root domain from data-domain and kept for 365 days.
- Backend on the same site (same root domain): read the cookie from the incoming request when creating the checkout.
- Backend on a different domain: the browser won’t send the cookie there.
Read it in the browser and include it in the request that creates the
checkout:
- The cookie can be missing (ad blocker, local development, cookieless mode). Never block or fail a checkout because it’s missing: pass the ID only when you have it.
Stripe Checkout
Pass the ID asclient_reference_id. For subscriptions, also put it in the
subscription’s metadata so each renewal invoice carries it.
- Merge, don’t replace. If the code already sets
metadataorsubscription_data.metadata, add thedf_visitor_idkey to those objects. Never drop their existing keys or fields. - If
client_reference_idis already used for something else (often the app’s own user ID), don’t overwrite it. Use the metadata keys only, and tell the user why. - Express / Node:
req.cookies.df_visitor_id(withcookie-parser) or parsereq.headers.cookie. In other stacks, read the request cookie the usual way.
Use the metadata key
df_visitor_id. Customer metadata is not read.
Stripe Payment Links (no backend): append the visitor ID to the link in
the browser:
https://buy.stripe.com/…?client_reference_id=<visitor id>.
Renewals: once a subscription’s first payment is attributed, later renewals
of the same subscription are attributed to the same visitor automatically.
LemonSqueezy
Put the visitor ID in the checkout’s custom data underdf_visitor_id:
- Checkout link or overlay: append
?checkout[custom][df_visitor_id]=<visitor id>, built in the browser from the cookie. If the link already has a?, use&. - Checkouts API: in the create-checkout body, add it at
attributes.checkout_data.custom.df_visitor_id, merged with any custom data already there.
meta.custom_data.
Fallback: match by email
If the visitor ID can’t be passed (e.g. a hosted checkout you can’t change), suggest sending the buyer’s email from the browser before redirecting to checkout, or on the success page:identify email, success-page URLs, and custom events with an amount
property.
Shopify
When the store is connected through Trfyx, the script saves the visitor ID on the cart and paid orders are attributed automatically. No code is needed.No-code tracking with HTML attributes
Good for marketing pages, CMS content and buttons where adding JavaScript is awkward.Click goals: data-trfyx-goal
- Sends a custom event named by the attribute value when the element, or anything inside it, is clicked or activated with Enter or Space.
- Each
data-trfyx-goal-<name>attribute becomes a property. Dashes in<name>become underscores:data-trfyx-goal-cta-variant→cta_variant. - Works on elements added later too.
- It counts the click, not success. For sign-ups or purchases prefer a JS event after success, or a pageview goal on the success page.
Element-seen events: data-trfyx-scroll
- Sends a custom event when the element becomes visible.
thresholdis the visible fraction from 0 to 1 (default 0.5).delayis the milliseconds it must stay visible (default 0).- Other
data-trfyx-scroll-<name>attributes become properties. The event also carriesscroll_percentage,thresholdanddelay. - It fires again each time the element leaves and re-enters the viewport.
Forms
Every valid form submit already sendsform_submission. Suggest giving
important forms an id (<form id="contact">) so a goal can single one out
with the property condition id = contact. It fires on submit even if the
server then rejects the form; for real leads, suggest a custom event on
success.
Script tag options
Add these to the same<script> tag only when the user wants that behaviour.
Each value is "true" unless shown otherwise.
For single elements in replays, use the class
rr-block (left out),
rr-mask (text masked) or rr-ignore (input ignored), or the attributes
data-replay-block, data-replay-mask, data-replay-ignore. Suggest these
for sensitive areas such as account details or billing pages.
Cookieless mode
If the user turns on Cookieless mode in Trfyx → Settings → Installation, the snippet’ssrc becomes https://app.trfyx.com/script.cookieless.js. Tell them the
trade-offs:
- No cookies. Visitors are counted by a server-side hash that changes daily, so a returning person counts as a new visitor each day.
- These don’t work in cookieless mode:
trfyx("props", …), the callback argument,data-allowed-hostnames,data-hash-based-routing, session replay, heatmaps, web vitals, the Users report, and revenue attribution by visitor ID (there is nodf_visitor_idcookie). Only the email fallback is left for revenue. - Custom events,
identify,payment,data-trfyx-goal,data-trfyx-scrolland the automatic events work the same.
Verify it works
Walk the user through this, or do it yourself if you can run a browser:- Open the site (deployed, or locally with
data-allow-localhost="true") with DevTools → Network, filtered bycollect. - On page load, a
POST …/api/collectwith"type":"pageview"returns 202. - Trigger the action. The request body should have
"type":"custom"andextraData.eventNameset to your event name, plus its properties. - If nothing is sent, read the console’s
Trfyx:messages:- “Tracking disabled on localhost” → add
data-allow-localhost="true"while testing. - “Missing website ID or domain” → fix the script tag.
- “Invalid property name” or “Maximum 10 custom parameters allowed” → fix the properties.
- “Tracking disabled inside an iframe” → expected inside frames.
- “automated client detected” → you’re in Playwright, Puppeteer or Selenium; test in a normal browser.
- “tracking disabled via localStorage flag” → see below.
window.trfyx is not a function→ add the queue stub.
- “Tracking disabled on localhost” → add
- Ad blockers can block analytics, so test in a clean browser profile.
- The event appears in the Trfyx dashboard under Events within seconds.
df_visitor_id / client_reference_id.
Excluding the user’s own visits: in the browser console on their site,
localStorage.setItem("df_ignore", "true"). Remove the key to undo. Office
IPs and staging hostnames can be excluded in Trfyx → Settings →
Exclusions.
Goals and funnels (in the Trfyx dashboard)
Events become conversions only when the user creates goals. You can’t do this from code, so finish by giving the user an exact list, for example:Goals → New goalFacts to get right:Funnels → New funnel: Visited sign-up → Signed up → Started trial
- Trigger Pageview, page path
/signup, display name “Visited sign-up”- Trigger Custom event, event name
signup_completed, display name “Signed up” (optional: Only count whenmethod=- Trigger Custom event, event name
trial_started, display name “Started trial”
- Goal triggers:
- Pageview matches a page path.
- Custom event matches the event name exactly, case-sensitively.
- Scroll depth counts when a visitor scrolls a given page at least N % down (1–100).
- Prefer a Pageview goal when the step has its own URL (
/thank-you). Use a Custom event when it doesn’t, or when the URL can be reached without the action really happening. - Paths: a leading
/is added automatically.*is a wildcard (/blog/*,/docs/*/install). Without*the match is exact. Scroll-depth goals take one exact path, no wildcard. - Only count when: up to 3
property = valueconditions, all of which must match on the same event, exactly. It works on all three triggers. It only sees properties the site sends, not country, browser, referrer or UTM tags. - The automatic events
form_submission,file_downloadandexternal_linkcan be custom-event goals, with drill-downs by page, file or URL. - Funnels: 2–8 steps, each a different goal, completed within 24 hours of the first step. With Allow other activity between steps on (usually right), other pages and events may happen in between. With it off, the steps must follow one another directly.
- Goals are queries over events already recorded, so a goal created today also shows past conversions for events the site was already sending.
Quick reference
- Visitor ID cookie for checkouts:
df_visitor_id - Stripe:
client_reference_idand/or metadatadf_visitor_id - LemonSqueezy:
checkout[custom][df_visitor_id]