# TrueMetriks Docs (full text) > Complete documentation for TrueMetriks server-side tracking and conversion attribution: setup, platform installs, ad and form integrations, session replay, dashboard reference, and the API. # Set up with your AI Source: https://docs.truemetriks.com/getting-started/ai-setup/ Copy one prompt into ChatGPT, Claude or any other AI and it walks you through the whole TrueMetriks setup, with the text steps or the video link at every stage. Setting TrueMetriks up is a handful of steps across a few different tools, and it is easier with something walking beside you. ## Copy this into your AI Copy the prompt below, paste it into ChatGPT, Claude or whichever AI you use, and tell it to help you set TrueMetriks up. It carries the whole setup and every link, so it can guide you from your first login to your first attributed sale. At each step it asks whether you want the steps written out or the video, and if you pick the video it hands you a link that opens straight on it. ```{ .text .docs-prompt } I use TrueMetriks, a server-side tracking and conversion attribution platform. It tracks my website visitors, ties every sale back to the ad that produced it, and sends those conversions back to my ad platforms. Walk me through setting it up from scratch, step by step. HOW TO GUIDE ME - Ask me ONE thing at a time and wait for my answer before moving on. - At the start of every step, ask me: "do you want the written steps, or the video?" If I pick the video, give me the exact link from the LINK MAP at the bottom and wait until I tell you I have watched it. If I pick the written steps, give them to me from this prompt. - Only ever give me links that appear in the LINK MAP below. Never invent a URL, never guess an anchor, and if I ask for something that has no link here, say so and point me at support instead. - Never ask me for my password, my API key, my MCP key, or any card details. You are reading me the steps; I am the one clicking in my own account. - Confirm each step worked before starting the next one. - Keep it short. I am doing this while looking at my screen, not reading an essay. BEFORE ANYTHING ELSE, ask me these three things and wait for my reply: 1. Do I already have a TrueMetriks account, or am I starting from zero? 2. What is my website built on? WordPress, Shopify, GoHighLevel, ClickFunnels, a custom or coded site, or something else. 3. Which ad platforms do I run? Facebook, Google Ads, TikTok, GA4, or none yet. Then take me through the steps below in order, skipping any I tell you is already done. STEP 1 - ACCOUNT AND FIRST SITE Only if I am starting from zero. - Sign up at https://app.truemetriks.com/signup with Google or with an email and password, then click the verification link we email. - The free trial is 7 days, needs no credit card, and has no event cap. - Signup then moves to the Setup step, which asks for four things: Website domain - the bare domain, e.g. example.com or sub.example.com. No https://, no path, no trailing slash. Workspace name - usually my company name. Timezone - pre-filled from my browser; this decides my reporting day. Currency - the currency my revenue and ROAS are shown in. - Click "Start free trial". The site and workspace are created together and I land in the dashboard. Warn me that timezone and currency decide how every later number reads, so it is worth getting right now. STEP 2 - CONNECT MY DOMAIN (CNAME) This makes tracking run first-party on my own subdomain, which is what gets it past most ad blockers. It is one DNS record. - Right after the site is created, the dashboard opens a "Connect your domain" popup with two fields, a Name and a Value, each with a copy icon. - If I closed it: a yellow reminder banner sits at the top of every dashboard page until the domain is verified, and the same fields live under Settings > Tracking. - At my DNS provider, add a CNAME record: Type = CNAME Name = the Name from the popup, exactly as shown (do not rename it, a name I make up will never pass Verify) Value = the Value from the popup, e.g. run.trmtks.com - On Cloudflare, set Proxy status to OFF, so it reads "DNS only" and the cloud is grey, not orange. A proxied record can fail with Error 1014. GoDaddy and most others have no proxy toggle, so there is nothing to switch. - Save, go back to TrueMetriks, and click Verify. - Propagation is usually 1 to 5 minutes, occasionally up to an hour. Ask me who my DNS provider is first. If it is Cloudflare or GoDaddy, offer the matching video. If Verify keeps failing after 30 minutes, tell me to check the Name and Value for typos, trailing dots and stray spaces, then send me to troubleshooting. STEP 3 - INSTALL TRACKING ON MY PLATFORM Use the answer I gave you about what my site is built on. Every snippet is copied from the dashboard at Settings > Tracking, where I pick the tab for my platform. WORDPRESS - In WP Admin, go to Plugins > Add Plugin, search "TrueMetriks", and install "TrueMetriks Analytics", then click Activate. - Open TrueMetriks Analytics in the WP Admin sidebar and click "Sign in with TrueMetriks". - On the authorize screen, pick my site from the Site dropdown and click Allow. That is the whole install, no snippet to paste. - On WooCommerce, tell me to place a test order while logged out; it should show in Events and Revenue within about 10 seconds. SHOPIFY - Copy the pixel code from Settings > Tracking > Shopify tab. - In Shopify Admin: Settings (bottom of the left sidebar) > Customer events > "Add custom pixel" (top right). - Name it TrueMetriks, click Add pixel. - In the pixel editor, find the Code section near the bottom, replace the default content with the copied code, then click Connect (top right). - Customer Privacy permission should be set to "Not required". - If my funnel spans two domains and Shopify is one of them, tell me Shopify is the one special case and send me to the Shopify cross-domain guide. GOHIGHLEVEL - Copy the Tracking snippet from Settings > Tracking > GoHighLevel tab. - In GoHighLevel: Sites > Funnels tab > open the funnel > Settings tab > Tracking & scripts > paste into "Head tracking code" > Save. - Repeat for every funnel. The snippet is the same each time. - CRITICAL, do not skip: in the same Settings tab, scroll to "Performance & compliance" and make sure "Optimize JavaScript" is switched OFF. It lazy loads the tracking snippet, and on a real funnel we measured the snippet loading 10,628ms after the page opened with it on, versus 401ms with it off. Visits get missed, one visitor gets counted as two, and the ad click id is lost, so sales stop being credited to the ad that produced them. It is per funnel and someone can turn it back on, so tell me to check it on every funnel and again if my numbers ever drop suddenly. - If I sell payment plans or subscriptions, I also need one automation that POSTs to the TrueMetriks webhook URL on every payment. Offer me that as its own step with its own video. - If I want form or calendar submissions tracked as events, offer the custom events step with its own video. CLICKFUNNELS - Ask me whether I am on ClickFunnels 2.0 or Classic (1.0). Both use the same snippet and the same webhook URL, but the menus differ. - Both need two pieces: the snippet in the funnel head, plus a webhook in my account for leads, purchases and refunds. - 2.0: Funnels > open the funnel > gear icon (top right) > Head Code > paste > Save. Repeat per funnel, head code is per funnel here. Then Workspace Settings > Webhooks > "Add new endpoint", paste the Webhook URL from the same dashboard card, and pick exactly these three event types: contact.created, orders/invoice.paid, orders/invoice.refunded. Under Endpoint scopes > Funnels, pick the funnels it should fire on, and leave Pages empty. Click Create endpoint. - Classic (1.0): open the funnel > Funnel Settings > paste into "Head Tracking Code" > Save, then "Manage Your Funnel Webhooks" > "+ New Webhook" and paste the same Webhook URL. - Mention the Checkout URL pattern: the tracker fires InitiateCheckout when the page URL contains the word "checkout". If my checkout URLs use another word such as order, cart or payment, I change that field on the install card and click Save. CUSTOM OR CODED SITE - One script tag in the head of every page, then a few calls where my events happen. - Send me to the custom install page, which has a second AI prompt built for exactly this. Tell me to open a separate chat, paste that prompt with my relevant code files, and come back here when the install is written. - Two things I always get wrong, so say them out loud: window.tmOrder items carry price in CENTS. A Purchase fired directly with window.tmFireEvent carries value in DOLLARS. SOMETHING ELSE - Do not improvise an install and do not tell me to use a tag manager workaround. Send me to the Other platforms page, where I fill in a short form. We build the integration for my platform free, whether I am on a trial or a paid plan, usually replying within 24 hours on business days and shipping most integrations 2 to 3 days after that first reply. Whatever path I took, finish this step by sending me to "Test your integration": open my live site in a private window, and the Live indicator on the dashboard Main page should turn green within a few seconds, with my session appearing in the Sessions list. If my funnel moves visitors between two separate domains I own, for example an advertorial on one domain and the checkout on another, tell me there is one switch to turn on and give me the cross-domain link. Subdomains and pages of one domain do not need it. STEP 4 - CONNECT MY AD PLATFORMS Use the answer I gave you. Do them one at a time, and do not start the next one until I confirm the last one is connected. FACEBOOK - Settings > Ad integrations > Facebook tab > "Connect Facebook". A Meta OAuth popup opens, five screens, about 90 seconds. If my browser blocks the popup, allow it and click the button again. - Then select my pixel: click "Add pixel", pick it from the dropdown, and review the Allowed events list. - Warn me not to revoke the app later in Facebook Business Settings. That silently stops my Conversions API events. Use the Disconnect button in the card instead, which cleans up both ends. - Offer these as optional extras, each with its own video: custom event mappings, the Purchase product filter, restricted categories, and connecting a Facebook catalog. FACEBOOK PAGE - ONLY IF I RUN LEAD FORM ADS, DM ADS OR COMMENT AUTOMATION Ask me first: do I run native lead form ads (an Instant Form filled in on Facebook or Instagram), click-to-message ads, or an ad that tells people to comment a keyword? If all three are no, skip this whole block. - Same card, at the bottom, below Catalog: "Connect a Page". Facebook has to be connected first and my tracking domain has to be verified, or the button stays disabled. - The Meta popup is the pixel flow plus TWO more screens, one after the other. Screen one lists my Pages: tell me to tick the PAGE THAT RUNS THE ADS, then Continue. Screen two lists my Instagram accounts as handles, not Page names: tell me to tick the INSTAGRAM ACCOUNT if I run Instagram DM ads or want Instagram comments, then Continue. Warn me that clicking through that second screen without ticking anything is the usual reason Instagram records nothing, and that nothing will tell me it happened. - That screen is where the Page is chosen. TrueMetriks connects what I granted and never asks me to pick it again. - Then check the card: my Page with a green Active chip and its capability chips. A muted Instagram chip with a dash means no linked professional Instagram account was found. - IF I SAID YES TO INSTAGRAM, walk me through the three settings on Instagram's side, one at a time, because each one fails silently: the account must be a professional account linked to that same Page, "Allow access to messages" must be on under Settings and privacy > Messages and story replies > Message controls > Connected tools, and the account must be public for comments to be recorded. - THEN TELL ME HOW THE EMAIL IS CAPTURED, because it is the part people miss and it decides whether a lead is a person or an anonymous conversation: A lead form ad carries the email itself, but ONLY if I used Meta's built-in Email field under Contact information in the form builder. A custom email question I typed myself is recorded and does not identify the person. Same for the built-in Phone number field. A DM ad has TWO ways to get an email, and I should use both. One, put an email question in the ad's "Generate leads" chat template in Ads Manager (the lead form inside the chat), wording the question with the word "email"; the person has to finish the flow. Two, ask the person to reply with their address in the ad's opening message, and we read it out of what they type. Both ways work on Messenger AND on Instagram Direct. Meta does not offer the chat template for every ad type, objective or country, so tell me to open the ad's message template and look, and to ask for a reply as well either way. A comment never carries an email, so if I run keyword automation, the private message it sends has to ask for one. - Tell me these two conversions are never sent back to Facebook, because Facebook already counts its own lead forms and DM ads, and re-sending would double count them. GOOGLE ADS - Settings > Ad integrations > Google Ads tab > "Connect Google Ads". A Google OAuth popup, three screens, about 60 seconds. - Then click "Add ad accounts", pick the account in the popup, and click Import. - Then add conversion action goals: "Add conversion action goal", fill the popup, and click Save changes, which is what actually creates them in Google Ads. A staged goal that never got Save changes does not exist yet. - If Google blocks the login asking for a passkey, send me to the passkey page. Approving a new passkey can take up to 7 days, so I should start it early. - Same warning as Facebook: disconnect from inside TrueMetriks, not from Google Account security settings. TIKTOK - Settings > Tracking tab > scroll to the Ad integrations panel > "Connect TikTok Ads", then approve the permission grant. About a minute. - Then add the pixel: "Add pixel", pick it from the dropdown, review the Allowed events, and Save changes. GA4 - Settings > Ad integrations > GA4 tab > "Add property". A property card expands with three fields: Friendly name (optional), Measurement ID, and API Secret. - The Measurement ID comes from GA4: Admin > Data streams > click the web stream > copy the Measurement ID. Create the web stream first if there is not one. - The API Secret comes from the same stream page: scroll to Measurement Protocol API secrets > Create > give it a nickname > Create > copy the value. - Paste both back into TrueMetriks and save. After each platform, offer me its test walkthrough so I can confirm conversions are actually arriving, rather than assuming they are. STEP 5 - THE CHROME EXTENSION This puts my real, attribution-corrected numbers as extra columns inside Facebook Ads Manager and Google Ads, so I never have to trust the platform's own reported conversions again. - Install "TrueMetriks Ad Overlay" from the Chrome Web Store: https://chromewebstore.google.com/detail/truemetriks-ad-overlay/hfkmfhbidmgfpfnignikimdniaebppdc - Click "Add to Chrome" and approve the permissions. - Pin the extension so the icon stays in my toolbar. - Click the icon, choose "Connect to TrueMetriks". It signs in with my existing app.truemetriks.com login, so there is no separate password. The popup then reads "Connected as" with my email. - Open Campaigns, Ad sets or Ads and the numbers appear. A blank overlay usually means a page it does not cover, or a table that has not finished rendering, so refresh and wait a moment. - GOOGLE ADS ONLY, and it is required: I have to enable the ID columns first, through Modify columns, on all three views, or the overlay has nothing to match on. - If I ask why a campaign row is bigger than its ad sets added up: that is correct, not a bug. A click carrying only a campaign id stays at campaign level and is never pushed down into one ad set, because a campaign has many and picking one would be inventing an answer. Send me the link rather than explaining it at length. WHEN THE SETUP IS DONE Confirm with me that I am seeing live sessions and at least one conversion arriving, then tell me the setup is complete. Then, and only then, offer me these as optional next steps, one line each, and give me the link for whichever I pick. Do not walk me through any of them unasked: - Stripe, Whop or PayPal, if I take payments there. - Calendly or Typeform, if I capture bookings or leads there. - Microsoft Clarity session replay and heatmaps, free, tagged with my events. - Webhooks, if my platform can POST to one. - The UTM builder, for consistent campaign links. - Cross-domain tracking, if my funnel spans two domains I own. Section overviews Pick an install path ........................ https://docs.truemetriks.com/platform-installs/ Pick an ad platform ......................... https://docs.truemetriks.com/ad-integrations/ Optional, only after the setup is done Stripe ...................................... https://docs.truemetriks.com/payment-integrations/stripe/ Whop ........................................ https://docs.truemetriks.com/payment-integrations/whop/ PayPal ...................................... https://docs.truemetriks.com/payment-integrations/paypal/ Calendly .................................... https://docs.truemetriks.com/form-integrations/calendly/ Typeform .................................... https://docs.truemetriks.com/form-integrations/typeform/ Microsoft Clarity ........................... https://docs.truemetriks.com/session-replay/clarity/ Webhooks .................................... https://docs.truemetriks.com/integrations/webhooks/ UTM builder ................................. https://docs.truemetriks.com/dashboard/utm-builder/ Cross-domain tracking ....................... https://docs.truemetriks.com/features/cross-domain-tracking/ Cross-domain with Shopify ................... https://docs.truemetriks.com/platform-installs/shopify-cross-domain/ When something is wrong Troubleshooting ............................. https://docs.truemetriks.com/troubleshooting/common-issues/ Support ..................................... support@truemetriks.com The app Sign up ..................................... https://app.truemetriks.com/signup Log in ...................................... https://app.truemetriks.com/login Chrome Web Store listing .................... https://chromewebstore.google.com/detail/truemetriks-ad-overlay/hfkmfhbidmgfpfnignikimdniaebppdc Start now by asking me the three questions. ``` ## What it will and will not do It walks you through your domain and CNAME, the tracking install for your platform, your ad platforms, and the Chrome extension, and it stops there. Payments, forms, session replay and webhooks are offered as links at the end rather than dragged into the walkthrough, so the setup stays short. It reads you steps and hands you links. It never touches your account, so nothing in the chat needs your password or any key, and the prompt tells the AI not to ask for one. If yours does anyway, that is your cue to close the tab. ## If your platform is not on the list WordPress, Shopify, GoHighLevel, ClickFunnels and custom sites are all covered. Anything else, and the prompt sends you to [Other platforms](../../platform-installs/other/) rather than letting the AI improvise an install. Tell us what you are on and we build the integration for you free, on a trial or a paid plan. ## Prefer to do it yourself? The same ground is covered by hand in the [setup roadmap](../complete-setup/), which links out to every step in order. # Complete setup Source: https://docs.truemetriks.com/getting-started/complete-setup/ Your step-by-step map from creating a TrueMetriks account to live conversion tracking: sign up, connect your domain, install the code, and link ad platforms. ## Your setup roadmap Work through the steps in order. The first three get data flowing; the last three turn that data into accurate attribution and ROAS reporting. ## The steps 1. **[Sign up and start your trial](../signup-and-trial/)** - Create your account, name your organization, set your time zone and currency, and add your first website to track. 2. **[Connect your domain](../cname-setup/)** - Add one CNAME record at your DNS provider so tracking runs first-party on your own subdomain and slips past most ad-blockers. 3. **[Install your tracking code](../install-first-tracker/)** - Add the snippet to your site using your platform: [WordPress](../../platform-installs/wordpress/), [Shopify](../../platform-installs/shopify/), [GoHighLevel](../../platform-installs/gohighlevel/), [ClickFunnels](../../platform-installs/clickfunnels/), or the [universal snippet](../../platform-installs/custom/) for anything else. 4. **[Connect your ad platforms](../../ad-integrations/facebook/)** - Link your ad accounts so conversions flow back to each one: [Facebook](../../ad-integrations/facebook/), [Google Ads](../../ad-integrations/google-ads/), [GA4](../../ad-integrations/ga4/), and [TikTok](../../ad-integrations/tiktok/). 5. **[Tag your links with the UTM builder](../../dashboard/utm-builder/)** - Build consistent UTM parameters for every campaign link so each click is attributed to the right ad. 6. **[Install the Chrome extension](../../features/chrome-extension/)** - See attribution-corrected ROAS and cost columns right inside Facebook Ads Manager and Google Ads. ## One extra switch if your funnel crosses two domains If a visitor moves between two separate domains you own during your funnel, for example an advertorial on one domain and the checkout on another, there is one switch to turn on so they stay one person instead of two strangers. Subdomains and pages of your own domain do not need it. Do it while you are installing rather than after your first campaign: [Cross-domain tracking](../../features/cross-domain-tracking/). ## What is next Start with step one, [Sign up and start your trial](../signup-and-trial/), and follow the links from there. # Signup and trial Source: https://docs.truemetriks.com/getting-started/signup-and-trial/ Create a TrueMetriks account, start your free 7-day trial, and add your first site to track. No credit card required, with unlimited events while you trial. ## Create an account Go to [app.truemetriks.com/signup](https://app.truemetriks.com/signup). Sign in with Google in one click, or enter an email and password of your choice. We send a verification link to your inbox. Click it once and your account is active. After you confirm your account, signup moves to the **Setup** step (below) where you add your first website. No credit card is required, and your 7-day trial is already active. ## What is included in the trial For 7 days you get **every paid feature, with no event cap**: - **Ad platform integrations**: [Facebook Pixel + Conversions API](../../ad-integrations/facebook/), [Google Ads](../../ad-integrations/google-ads/) conversions, GA4 forwarding, TikTok (pending platform approval), Stripe and Whop revenue ingest. - **Tracking surfaces**: WordPress plugin, Shopify Custom Pixel, ClickFunnels, GoHighLevel, the universal JS snippet, and the [Chrome extension overlay](../../features/chrome-extension/) for ad spend and ROAS columns. - **Attribution + analytics**: every dashboard page, chart, and filter. Funnels, journeys, attribution models, FB Catalog sync, all included. - **No event cap**: unlimited pageviews, leads, and purchases across unlimited sites for the full 7 days. - **Team + multi-site**: invite teammates with role-based access, create multiple sites under one workspace. When the trial ends, pick a plan that matches your monthly event volume. Your data and integrations carry over without re-setup. ## Add your first site The **Setup** screen creates your first website and workspace together. It asks for four things: - **Website domain** - the domain you want to track. Use the bare domain (`example.com` or `sub.example.com`), no `https://`, no path, no trailing slash. - **Workspace name** - a friendly name for your workspace, usually your company name. - **Timezone** - pre-filled from the time zone we detect; change it if your reporting day should follow a different one. - **Currency** - the currency your revenue and ROAS figures are shown in. Click **Start free trial**. Your site and workspace are created and you land in the dashboard with your 7-day trial active. The next page shows the tracking install instructions for whichever platform you pick. ## Next step Continue to [CNAME setup](/getting-started/cname-setup/) to point a first-party subdomain at TrueMetriks. Then [install your first tracker](/getting-started/install-first-tracker/) - WordPress, Shopify, ClickFunnels, GoHighLevel, or the universal JS snippet. # CNAME setup Source: https://docs.truemetriks.com/getting-started/cname-setup/ Copy the CNAME record TrueMetriks shows for your site into your DNS provider (Cloudflare, GoDaddy, or any other), then click Verify. Exact steps and screenshots. ## Right after you add your site Right after you [create a site](../signup-and-trial/), TrueMetriks shows this. Copy the **Name** and the **Value** into your DNS provider exactly as shown (do not rename them), turn the proxy **off**, save, then come back and click **Verify**. Below are the exact steps for the two most common providers. ## Cloudflare Open your zone, go to **DNS → Records**, and click the blue **Add record** button. In the panel that opens: 1. **Type**: choose `CNAME`. 2. **Name**: paste the **Name** from your dashboard (`haven` in the example). 3. **Target**: paste the **Value** from your dashboard (`run.trmtks.com`). 4. **Proxy status**: turn it **off** so it reads **DNS only** (grey cloud, not orange). 5. Click **Save**. Then go back to TrueMetriks and click **Verify**. ## GoDaddy Open **My Products → Domains**, click your domain, open the **DNS** section, and click **Add New Record**. 1. **Type**: choose `CNAME`. 2. **Name**: paste the **Name** from your dashboard. 3. **Value**: paste the **Value** from your dashboard. 4. Leave **TTL** on the default (1 Hour is fine). 5. Click **Save**. GoDaddy has no proxy toggle, so there is nothing else to switch off. Go back to TrueMetriks and click **Verify**. ## Any other DNS provider Every provider works the same way: add a **CNAME** record, paste in the **Name** and **Value** from your dashboard, turn off the proxy if there is one, then save and click **Verify**. ## If you closed the popup No problem. Until the CNAME is verified, a yellow reminder banner stays pinned to the top of every dashboard page, and the same details live under **Settings → Tracking**. Either one reopens the exact fields above. ## Video guides **Cloudflare** **GoDaddy** ## Troubleshooting - Verify still failing after a few minutes? Check that the **Name** and **Value** match your dashboard exactly, with no trailing dots or extra spaces. - On Cloudflare, make sure the record is **DNS only** (grey cloud), not **Proxied** (orange cloud). - Still stuck? See [Troubleshooting → Common issues](/troubleshooting/common-issues/). # Install your tracking code Source: https://docs.truemetriks.com/getting-started/install-first-tracker/ Install your TrueMetriks tracking code with the guide that matches your platform: WordPress, Shopify, GoHighLevel, ClickFunnels, or a custom site. ## Pick your install path ## After the install Once any of the five paths is complete, open your live site in a private / incognito window, then watch the **Live** indicator on the dashboard's [**Main** page](../../dashboard/main/) - it should turn green within a few seconds and your session appears in the [**Sessions** list](../../dashboard/sessions/) with `direct` (or the UTM string you came in with) as the source. If nothing shows up after 30 seconds, jump to [Troubleshooting](/troubleshooting/common-issues/). ## If your funnel crosses two domains Install the same snippet on every domain the funnel uses, then turn on one switch so a visitor moving between them stays one person: [Cross-domain tracking](../../features/cross-domain-tracking/). This is only for two separate domains you own, like an advertorial domain and a store domain. Subdomains and pages of your own domain are already tracked as one site. # WordPress plugin Source: https://docs.truemetriks.com/platform-installs/wordpress/ Install the TrueMetriks WordPress plugin from WP Admin: search TrueMetriks under Plugins > Add Plugin, click Install Now, activate, and track pageviews, leads, and WooCommerce sales. ## Video guide ## Open the Plugins menu in WP Admin Log in to your WordPress admin. In the left sidebar, hover **Plugins**, then click **Add Plugin** from the flyout. If your WP Admin is set to a compact sidebar, the flyout appears as soon as you hover. On a wider sidebar you can also click **Plugins > Installed Plugins**, then click the **Add Plugin** button next to the page title. Both routes land you on the same Add Plugins screen. ## Find the TrueMetriks plugin On the Add Plugins screen, type **TrueMetriks** into the **Search Plugins** box in the top right. The **TrueMetriks Analytics** plugin by TrueMetriks appears in the results. ## Install and activate Click **Install Now** on the TrueMetriks Analytics plugin. WordPress downloads and installs it in a few seconds, then the button turns into **Activate** - click it to turn the plugin on. ## Connect the plugin to TrueMetriks After activation, open **TrueMetriks Analytics** in the WP Admin sidebar. You will see the connect screen below. Click **Sign in with TrueMetriks**. You will be taken to your TrueMetriks dashboard to authorize the plugin. Pick the site you want to connect from the **Site** dropdown, then click **Allow**. That is it - the plugin is connected and tracking. For WooCommerce stores, place a test order while logged out to check it - the order should appear in **Events** and **Revenue** within about 10 seconds. ## How to test After connecting the plugin, the next step is verifying everything is firing correctly. See [Test your integration](/platform-installs/test-your-integration/) for the full walkthrough. Something not firing? See [Troubleshooting](/troubleshooting/common-issues/). # Shopify Custom Pixel Source: https://docs.truemetriks.com/platform-installs/shopify/ Install the TrueMetriks Shopify Custom Pixel from your dashboard, paste it into Shopify Admin under Customer events, and track pageviews, checkouts, and orders. ## Video guide ## Copy the pixel code from your dashboard In your TrueMetriks dashboard, go to **Settings > Tracking**, pick the **Shopify** tab, and click **Copy** on the code block. ## Open Settings in Shopify Admin Log in to your Shopify Admin. At the bottom of the main left sidebar, click **Settings**. ## Open Customer events The Settings panel slides in from the left. Scroll down and click **Customer events**. ## Click Add custom pixel At the top right of the Customer events page, click the dark **Add custom pixel** button. ## Name it TrueMetriks In the Add custom pixel dialog, type **TrueMetriks** in the Pixel name field, then click **Add pixel**. ## Paste the code and Connect You land on the pixel editor. Find the **Code** section near the bottom of the page and replace the default content with the code you copied from your TrueMetriks dashboard. Then click **Connect** in the top right. ## How to test After connecting the pixel, the next step is verifying everything is firing correctly. See [Test your integration](/platform-installs/test-your-integration/) for the full walkthrough. Something not firing? See [Troubleshooting](/troubleshooting/common-issues/). # Cross-domain tracking with Shopify Source: https://docs.truemetriks.com/platform-installs/shopify-cross-domain/ Add the small theme snippet Shopify needs for cross-domain tracking. Your Custom Pixel keeps sending events; this snippet only does the handoff between domains. If your funnel uses Shopify together with a second, separate domain, for example an advertorial or landing page domain that sends visitors on to your Shopify store, Shopify needs one small extra step beyond the [Custom Pixel install](../shopify/). This page covers only that extra step; read [Cross-domain tracking](../../features/cross-domain-tracking/) first for what the feature does and how to turn it on. **Every Shopify store in a cross-domain funnel should have this snippet.** It is one line, it covers both directions, and it does not depend on anything else being up to date. ## Why Shopify is the one special case A Shopify Custom Pixel runs inside a locked sandbox. It has no `document.cookie` access and no DOM access, so it cannot add anything to a link a visitor is about to click, and it cannot write a cookie on the store's own domain. That is a restriction Shopify puts on every Custom Pixel, not something TrueMetriks chose. It means the pixel can report events, but it can never be the thing that hands a visitor off between two domains. So Shopify needs a second, small snippet, pasted into your theme, that does only the handoff. Your existing Custom Pixel does not change at all, and keeps sending pageview, checkout, and order events exactly as it does today. ## Get the snippet 1. Make sure [Cross-domain tracking](../../features/cross-domain-tracking/) is turned on for the site, in **Settings > Websites > your site > Tracking**, with your other domain listed and saved. 2. Staying on the **Tracking** tab, pick the **Shopify** card. 3. Below the Custom Pixel code you will see a section titled **"Click here if you're doing cross-domain tracking with Shopify"**. It only appears once cross-domain tracking is switched on for the site; if you do not see it, go back and turn the toggle on first. 4. Open it and click **Copy** on the one-line snippet inside. It looks like this: ```html ``` Your own snippet will have your site's actual tracking host in place of `t.yourdomain.com`. ## Install it in your theme 1. In Shopify Admin, go to **Online Store > Themes**. 2. On your **live** theme, click **Edit code**. A draft theme is the single most common reason this ends up doing nothing. 3. Open **`theme.liquid`**. 4. Paste the script tag on its own line, just before the closing `` tag. 5. Click **Save**. That is the whole install. It runs on every storefront page the theme renders. Shopify's own checkout pages cannot be edited and do not need this snippet; your Custom Pixel's events already pick up the visitor id the theme snippet sets once the visitor is on your storefront. ## Turn password protection off **A password-protected Shopify store breaks cross-domain tracking**, snippet or no snippet. Shopify redirects the visitor to `/password` and drops the query string on the way, which takes the handoff with it. Check it in Shopify Admin under **Online Store > Preferences**. This catches people out on development stores in particular, where password protection is locked on until the store is on a paid plan. If you are testing a cross-domain funnel against a development store, that is the first thing to rule out. ## What it does, and does not, do - It adopts an incoming handoff from another listed domain, so the visitor keeps the same identity when they land on your Shopify store. - It tags outgoing links to your other listed domain or domains, so a visitor leaving your Shopify store for another domain in your funnel carries the handoff with them. - It fires no events of its own. Your Custom Pixel is the only thing sending events; this snippet only sets up the identity that the pixel then reports. ### If your store is only ever the destination There is one case where the theme snippet is not strictly required, and it is worth knowing about even though the advice does not change. The current Custom Pixel reports the page a visitor came from. So when someone lands on **another** domain first and then clicks through **into** your Shopify store, the store can pick the handoff up from that alone, with no theme snippet. Your store as the **destination** works either way. The other direction does not, and never will. When a visitor starts **on** your Shopify store and clicks **out** to another domain in your funnel, something has to tag that outgoing link, and only unsandboxed code can do that. That is the theme snippet's whole job. **Two reasons to install it anyway, which is why this is a note and not a recommendation.** 1. **It only applies once you have re-copied your pixel.** The Custom Pixel is code you pasted into Shopify, not a hosted script we can update for you. If you pasted yours before this behaviour shipped, your store is still running the old version and does not report the referring page at all. Re-copy your Custom Pixel from the **Tracking** tab's Shopify card and paste it over the old one in Shopify Admin to pick it up. 2. **It is one line and it covers both directions**, so it keeps working the day you add a second step that leaves the store, and it does not depend on when anybody last re-pasted anything. ## Check it worked There is no status badge for this, on purpose. Run the two-minute Sessions test from the main guide: [Test it yourself](../../features/cross-domain-tracking/#test-it-yourself). One session containing pages from both your Shopify store and your other domain means it is working. If it still shows as two sessions, in order: confirm the script tag is saved in `theme.liquid` on the **live** theme, confirm password protection is off, then work through [Cross-domain tracking troubleshooting](../../features/cross-domain-tracking/#troubleshooting). # GoHighLevel install Source: https://docs.truemetriks.com/platform-installs/gohighlevel/ Install the TrueMetriks tracking snippet on every GoHighLevel funnel, configure the Checkout URL pattern, and add a webhook for payment plans or subscriptions. ## Video guides ### Install GoHighLevel tracking ### Payment plan and subscription automation ### Custom events for forms and calendars ## Copy the tracking snippet from your dashboard In your TrueMetriks dashboard, go to **Settings > Tracking**, pick the **GoHighLevel** tab, and click **Copy** on the Tracking snippet. **Checkout URL pattern.** This is the word the tracker looks for in your URL to identify a checkout page. The default is `checkout`. If your funnels use a different word in their checkout URLs, change it in this field and click **Save**. The card also has three expandable options - one for payment plans and subscriptions, one for firing [custom events for your forms and calendars](#fire-custom-events-for-forms-and-calendars), and one for Typeform or Calendly. The first two are covered below. ## Open the funnel in GoHighLevel In the GoHighLevel left sidebar, click **Sites**. At the top of the Sites page, click the **Funnels** tab. Click the funnel you want to track. You will repeat this for every funnel in your account - the snippet is the same. ## Paste the snippet into Head tracking code Inside the funnel, click the **Settings** tab. Scroll down to **Tracking & scripts** and paste the snippet you copied into the **Head tracking code** field. Then click **Save**. Repeat for each funnel. ## Turn OFF Optimize JavaScript Stay in the funnel's **Settings** tab and scroll down to **Performance & compliance**. Make sure **Optimize JavaScript** is switched **off**. This one setting breaks tracking, and it is switched on per funnel, so a funnel that worked yesterday can stop working the moment somebody turns it on. **What it does to you.** GoHighLevel's own description is "custom javascript and html codes added will be lazy loaded". Your tracking snippet is custom JavaScript, so it gets lazy loaded with everything else, and instead of running as the page opens it runs many seconds later. On a real customer funnel we measured the snippet loading **10,628ms** after the page opened. With the setting off, the same funnel loaded it in **401ms**. **What breaks in those ten seconds.** The visitor is on your page and TrueMetriks is not running yet, so: - Plenty of people land, read, and click through before the tracker ever starts, and those visits are never recorded at all. - A visitor who moves on quickly can be counted as two separate people, because the click that identified them arrived before the tracker was ready. Their landing page and their thank-you page end up in two different sessions. - The ad click id can be missed, so the sale is not credited to the ad that produced it, and the conversion sent back to Facebook, Google Ads or TikTok carries no click id. None of this looks like a broken install. The snippet is in the right place and the funnel works fine, you just get thin, split, badly attributed data. **Leave it off and keep it off.** Check it on every funnel you track, and check it again if your numbers suddenly drop or your sessions start splitting in two. **Image Optimization** and **GDPR compliant fonts** in the same panel do not affect tracking, so set those however you like. ## Payment plans and subscriptions (webhook) If you sell payment plans or subscriptions, add one GoHighLevel automation that POSTs to the TrueMetriks webhook URL each time a payment lands. ### Grab the webhook URL Back in your TrueMetriks dashboard, on the same GoHighLevel install card, expand **Click here if you have payment plans or subscriptions** and click **Copy** on the webhook URL. ### Open Automation in GoHighLevel In the GoHighLevel left sidebar, click **Automation**. ### Create a workflow from scratch Top right, click **+ Create Workflow**, then **+ Start from Scratch** in the dropdown. ### Add the Payment Received trigger In the new workflow, add a trigger and pick **Payment Received** from the list. Leave the trigger name as **Payment Received** (or change it if you want), then click **Save Trigger**. ### Add the Webhook action Below the trigger, add an action and pick **Webhook** (the normal one - not Custom Webhook). Configure it like this: - **Method**: `POST` - **URL**: paste the webhook URL you copied from your dashboard Then fill in **CUSTOM DATA** with the keys in the table below and click **Save Action**. Leave **Headers** empty. Fill in Custom Data on **every** workflow, including one built on GoHighLevel's own **Payment Received** trigger, and always map `product` to the merge field that holds the product **name**. Payment Received does send the amount, currency and transaction id on its own, but it identifies the product by an internal id rather than by its name, so without that one mapping your reports show a long code instead of what was sold. ### Fill in Custom Data with these exact keys The key names on the left are not free-form: TrueMetriks reads the sale from these keys, so they must be spelled exactly as shown. This matters most on a workflow that carries no standard payment data at all, such as one started by an **Inbound webhook** trigger (your payment processor, a Zap, or your own backend posts into GoHighLevel) where you build the sale yourself from custom values, but the same keys apply on every workflow. The left column is the key (fixed, copy it exactly). The right column is whichever merge field in **your** workflow holds that piece of data. | Key | Required | What the value must contain | |-----|----------|------------------------------| | `email` | Required | The buyer's email address. This is how the sale is matched to the person and their ad click. | | `amount` | Required | The amount of **this one payment**, as a plain number (`1995` or `1995.00`). Currency symbols and thousands separators are accepted (`$1,995.00`). | | `currency` | Required | The 3-letter currency code, for example `USD`, `GBP`, `EUR`. If you leave it out, `USD` is assumed. | | `product` | Required | The name of what was bought, for example `1-1 Coaching Extension`. This is the product name shown in your reports. If you leave it out, or map it to a product or price id instead of the name, your reports show that code instead of the product and any Facebook custom conversion that matches on the product name will not fire. | | `order_id` | Recommended | The unique id of this payment or transaction. It is what stops the same sale being counted twice. If you leave it out, TrueMetriks generates one. | | `first_name`, `last_name`, `phone` | Optional | Extra buyer details, which improve the match rate on Facebook and TikTok. | ### Two rules that stop wrong revenue **1. `amount` must never arrive empty.** If the merge field you mapped to `amount` has no value for that payment, TrueMetriks falls back to the next money-like field in the payload and can record that number as your sale. **2. Send only one money field.** Delete every other key that holds a number of money from the Custom Data list, for example `revenue`, `total`, `cash_collected`, `payments`, or a lifetime-value field. A running total is not the sale. This is the single most common cause of a purchase showing a much larger figure than the payment that was actually taken: a workflow mapped `amount` to a field that came through empty, and a `revenue` key next to it carried the customer's total cash collected to date, so that total was recorded as the sale. Keep the list to the keys in the table above and the amount in your reports will always be the amount that was charged. ### Publish the workflow Top right, flip the toggle from **Draft** to **Publish**, then click **Save**. For reference, your published automation should look like this: ## Fire custom events for forms and calendars Custom events let you fire an extra, named Facebook event for one specific form or calendar, matched by its name, so you can build an audience or optimize a campaign around that one source. By default your standard Lead and Schedule still fire and record alongside it, and you can optionally switch that off so only your custom event fires for that one matched form or calendar (see **Also fire the standard event** below). For example: a `newsletter_lead` event only for your newsletter form, or a `strategy_call_booking` event only for your strategy-call calendar. **Video guide** ### Open the custom events option and click Create On the GoHighLevel install card in your dashboard, expand **Click here if you want to fire custom events for your GoHighLevel forms and calendars**, then click **Create**. ### Fill in the Create custom event popup - **Standard event.** Pick **Lead** for a form or **Schedule** for a calendar. This is the most important choice: **Lead is for forms, Schedule is for calendars.** - **Custom event name.** Type any name you want, for example `newsletter_lead`. This is the name the event shows under in Facebook. Use letters, numbers and underscores. - **Fire when.** Leave it on **Always**, or choose **New customers only** / **Returning customers only** to fire just for first-time or repeat contacts. See [New vs returning customers](/features/new-vs-returning/) for what those mean. - **Source filter.** This is where you tell TrueMetriks which form or calendar to match. Type the **name** of the form (for Lead) or the calendar (for Schedule). The match is case-insensitive and a "contains" match. Leave it blank to fire the custom event on every Lead (or every Schedule) instead of one specific source. - **Also fire the standard event.** This checkbox only appears once you have typed a Source filter. Leave it **on** (the default) to fire and record both the standard event and your custom event, exactly as before. Turn it **off** to fire and record **only** your custom event for that matched source: the standard `Lead` or `Schedule` will not fire to Facebook and will not show up in your TrueMetriks reports. Use this when one specific form should count as its own conversion instead of a Lead, for example a registration form counted as `complete_registration`. ### Where to find the form or calendar name For a **form**, open **Sites > Forms** in GoHighLevel and edit the form. The name shown at the top is the source - copy it into the Source filter. For a **calendar**, open **Calendars** in GoHighLevel, edit the calendar, and look at **Calendar name** under Basic details. Copy that into the Source filter. Paste the name into the Source filter exactly as it appears, then click **Create**. ### What you get after clicking Create The event appears under **Facebook > Custom events** in your dashboard, tagged **GoHighLevel** with the source it matches. From here you can edit when it fires or remove it. TrueMetriks also creates a matching **Custom Conversion** inside Facebook Ads Manager (Events Manager > your pixel > Custom conversions). It starts **Inactive** and turns Active once the first matching event lands. Give it 30 to 60 seconds before you test. ### Test it Submit the form (or book the calendar) you filtered on. In Facebook **Events Manager > Test events** you will see both the standard event and your custom event arrive together - **Lead** plus your custom name for a form, **Schedule** plus your custom name for a calendar. ## Custom coded forms and calendars If you built your own form or your own booking widget on a GoHighLevel page instead of using GoHighLevel's, TrueMetriks still tracks the visitor but it never learns that they submitted anything, so no **Lead** or **Schedule** conversion is recorded. One line of your own JavaScript fixes that. ### When you need this Only when the form or the booking widget is **custom coded**: your own HTML and JavaScript, or a third-party embed that is not a GoHighLevel form and not a GoHighLevel calendar. **A normal GoHighLevel form or calendar needs none of this.** Those record their Lead and Schedule automatically, and adding the code below to one of them would count the same conversion twice. Only use it where you built the form or the booking widget yourself. The symptom, if you are not sure: the person shows up in your dashboard with their email and phone and you can see their session, but no Lead (or no Schedule) ever appears for them. ### Add the helper to your page Paste this once into the funnel's **Head tracking code**, **underneath** your TrueMetriks snippet. A custom code block on the page works too, as long as it comes after the snippet. ```html ``` [Optimize JavaScript](#turn-off-optimize-javascript) must be **off** for any of this to work. With it on your snippet is lazy loaded, so it is not there yet when your form submits and the conversion is lost. ### Fire Lead from your custom form In your own success handler, after the submission has actually gone through: ```js // Form submitted successfully if (!window.__tmLeadSent) { window.__tmLeadSent = true; tmSendConversion("Lead", { email: "jane@example.com", phone: "+15551234567", first_name: "Jane", last_name: "Doe" }); } ``` Replace the example values with the ones the person actually typed into your form. ### Fire Schedule from your custom calendar Exactly the same shape in your confirmed-booking handler, with the event name changed to `Schedule`: ```js if (!window.__tmScheduleSent) { window.__tmScheduleSent = true; tmSendConversion("Schedule", { email: "jane@example.com", phone: "+15551234567", first_name: "Jane", last_name: "Doe" }); } ``` ### What to put in the contact details - **`email` is the one that matters.** It is what ties the conversion to the visitor's session and to the ad click that brought them in. Send the real address they typed. A merge tag that has not been filled in, such as `{{contact.email}}` arriving as literal text, is ignored. - **`phone`** should carry the country code, for example `+15551234567`. - **`first_name` and `last_name`** are optional and can be left out. - Every field is optional in the sense that an event with no email still records, but it cannot be matched back to the person nearly as reliably, so send the email whenever you have it. - **`Lead` and `Schedule` are the only two names to use here.** They are the same conversions the dashboard and Facebook already understand, so they show up in your reports and fire to every connected ad platform exactly like a native GoHighLevel form would. ### Two rules that stop double counting **1. Fire it once, and only when the submission actually succeeded.** Put the call in your success path, after your own save or booking request comes back OK, never on the button click. A hand-fired conversion has no safety net here: two calls are two conversions in your reports and two conversions sent to your ad platforms. The `if (!window.__tmLeadSent)` guard in the examples is what stops an impatient double click sending it twice, so keep it in. **2. Do not also send the same form through a receiving webhook.** If you have a GoHighLevel workflow POSTing this same submission to a TrueMetriks webhook URL, pick one or the other. Doing both records the conversion twice. ## How to test After installing the snippet (and the webhook, if you set one up), the next step is verifying everything is firing correctly. See [Test your integration](/platform-installs/test-your-integration/) for the full walkthrough. Something not firing? See [Troubleshooting](/troubleshooting/common-issues/). # ClickFunnels install Source: https://docs.truemetriks.com/platform-installs/clickfunnels/ Install the TrueMetriks tracker on a ClickFunnels funnel (Classic or 2.0) and capture leads, purchases, refunds, subscriptions, and payment plans via webhook. ## What gets tracked ClickFunnels needs two pieces: a tracking snippet in your funnel head (PageView and InitiateCheckout) and a webhook in your ClickFunnels account (Lead, Purchase including every recurring subscription and payment-plan charge, and Refund). Both ClickFunnels versions use the same setup and the same Webhook URL, so skip to the version you use below. ## ClickFunnels 2.0 ### Copy the tracking snippet from your dashboard In your TrueMetriks dashboard, go to **Settings > Tracking**, pick the **ClickFunnels** tile, and click **Copy** on the Tracking snippet. **Checkout URL pattern.** By default the tracker fires InitiateCheckout whenever the page URL contains the word `checkout`, so make sure your checkout step has `checkout` in its URL. If you use a different word (`order`, `cart`, `payment`, etc.), change the value in the **Checkout URL pattern** field on this card and click **Save**. ### Open Funnels in ClickFunnels In ClickFunnels, click **Funnels** in the left sidebar. On the Funnels page, click the funnel you want to track. ### Open Funnel Settings Once you are inside the funnel, click the **gear icon** in the top right corner. That opens **Funnel Settings** in a side panel. ### Paste the snippet into Head Code In the Funnel Settings panel, scroll to **Head Code**, paste the Tracking snippet you copied earlier, then click **Save**. Repeat this on every funnel you want tracked. ClickFunnels 2.0 head code is scoped per-funnel, not workspace-wide. ### Open Workspace Settings To capture leads, purchases, and refunds you also need to install a webhook. In the ClickFunnels 2.0 left sidebar, scroll to the bottom and click **Workspace Settings**. ### Open Webhooks Inside Workspace Settings, scroll the menu and click **Webhooks** near the bottom. ### Click Add new endpoint You land on the Webhooks endpoints page. Click **Add new endpoint** to open the endpoint form. ### Copy the Webhook URL from your dashboard Before filling the endpoint form, jump back to your TrueMetriks install card and copy the **Webhook URL** (the second value on the card, under the tracking snippet). ### Fill in the endpoint form Back in ClickFunnels, fill the endpoint form in this order: 1. **Name** - call it `TRUEMETRIKS PURCHASE TRACKING` (or anything you will recognize later). 2. **URL** - paste the **Webhook URL** you just copied. 3. **Event types** - pick the three events TrueMetriks needs: `contact.created`, `orders/invoice.paid`, and `orders/invoice.refunded`. 4. **Endpoint scopes > Funnels** - pick the funnel (or funnels) you want this endpoint to fire on. Leave **Pages** empty. Click **Create endpoint** at the bottom. ## ClickFunnels Classic (1.0) ### Open your funnel In the top navigation click **Click Funnels**. From the Funnels list click the funnel you want to track. ### Open Funnel Settings Inside the funnel, click the **SETTINGS** tab in the top right of the funnel toolbar. ### Paste the snippet into Head Tracking Code The code you need is the **Tracking snippet** on your TrueMetriks install card - the same snippet you would use for CF 2.0. Copy that snippet, then on the funnel Settings page scroll down to **Head Tracking Code**, paste it in, and click **Save Funnel Settings** at the bottom of the page. ### Open Manage Your Funnel Webhooks Stay on the same Settings page and scroll further down to the **Webhooks** block. Click **Manage Your Funnel Webhooks**. ### Click + New Webhook On the Active Funnel Webhooks page, click **+ New Webhook** in the top right. ### Copy the Webhook URL from your dashboard Before filling the form, jump back to your TrueMetriks install card and copy the **Webhook URL** (the second value on the card, under the tracking snippet). ### Fill in the webhook form Back in ClickFunnels, fill the New Funnel Webhook form in this order: 1. **URL** - paste the **Webhook URL** you just copied. 2. **Event** - keep `All Events` (simplest). If you prefer to subscribe explicitly, the minimum needed is `contact_created` and `purchase_created`. Refunds and recurring charges come through `purchase_updated`, which is included in `All Events`. 3. **Version** - `1` 4. **Adapter** - `attributes` Click **Create Funnel Webhook** at the bottom. Classic ClickFunnels scopes webhooks per-funnel. If you have multiple funnels, repeat these steps on each one - paste the same Webhook URL every time. ## How to test After installing the snippet (and the webhook), the next step is verifying everything is firing correctly. See [Test your integration](/platform-installs/test-your-integration/) for the full walkthrough. Something not firing? See [Troubleshooting](/troubleshooting/common-issues/). # Custom install Source: https://docs.truemetriks.com/platform-installs/custom/ Install the TrueMetriks tracker on any custom site (HTML, React, Vue, Astro) with one head snippet, then fire leads, checkouts, and purchases from your code. ## What gets tracked - **PageView** on every page load and SPA route change (automatic) - **InitiateCheckout** when the URL contains your configured pattern (automatic) - **Lead** when you call `tmFireEvent("Lead", ...)` (manual - from your form handlers) - **Schedule** when you call `tmFireEvent("Schedule", ...)` (manual - from your booking confirmations) - **CompleteRegistration** when you call `tmFireEvent("CompleteRegistration", ...)` (manual - from your signup handler, custom installs only) - **Purchase** when you set `window.tmOrder` on your thank-you page, or fire `tmFireEvent("Purchase", ...)` per charge in an upsell funnel - **Any custom event name** you call `tmFireEvent("YourEventName", ...)` with Lead, Schedule, and Purchase are forwarded to [Facebook](../../ad-integrations/facebook/), [Google Ads](../../ad-integrations/google-ads/), [TikTok](../../ad-integrations/tiktok/), and [GA4](../../ad-integrations/ga4/). ## Video guide ## Set this up with AI Most custom installs go faster with an AI pair. Copy the prompt below and paste it into Claude / ChatGPT / Cursor / etc. along with the relevant files from your codebase. The AI will ask you the follow-up questions it needs, then write the integration for you. ```{ .text .docs-prompt } I want to install TrueMetriks tracking on my website. TrueMetriks is an analytics + ad-platform fanout tracker - one install fires events to my dashboard AND to every ad platform I have connected (Facebook, Google Ads, TikTok, GA4). The install has two parts: 1. INSTALL SNIPPET (one line, in on every page): Replace MY-CNAME with the CNAME hostname shown on my TrueMetriks install card. 2. AT RUNTIME, the snippet exposes three globals: window.tmFireEvent(name, customData, userData) - Fires any event. Standard names: "Lead", "InitiateCheckout", "Schedule", "Purchase", "CompleteRegistration", "Identify". Any other string is treated as a custom event. - customData: optional object with event-specific custom payload. For Purchase: { value, currency, order_id, content_ids, num_items, items }. For Lead/Schedule/custom: optional context, e.g. { source: "newsletter" }. For CompleteRegistration: all optional - { content_name, status, value, currency }; send currency only if you send value. - userData: optional object with PII for ad-platform match quality: { email, first_name, last_name, phone, city, state, zip, country, dob, gender, externalId }. Hashed server-side before fanout. window.tmIdentify(email, traits) - Persists the visitor's identity for the session so every subsequent event carries email + traits to ad-platform fanout. - traits: { first_name, last_name, phone, city, state, zip, country, dob, gender, externalId, address } - all optional. - Call once after you know the visitor's email (form submit, login, etc). window.tmOrder = { order_id, email, currency, items } - Set this object on the thank-you / order-confirmation page. The tracker polls window.tmOrder every 500ms for up to 30 seconds and auto-fires Purchase + Identify the moment it appears. - order_id (required, unique string), email (recommended), currency (defaults to "USD"), items (required array): each item is { sku, name, price (in CENTS), qty, category?, brand?, variant?, image_url? }. - Same-page upsells: mutate order_id + items after each upsell. The tracker keeps polling and dedups by order_id in sessionStorage. 3. AUTOMATIC BEHAVIOUR (you do NOT call these manually): - PageView on every load and SPA route change - InitiateCheckout when the URL contains the configured Checkout URL pattern (default: "checkout") - First-party session cookie on the CNAME domain - utm_*, gclid, fbclid, ttclid, wbraid, gbraid capture on first touch WHAT I WANT YOU TO DO: - Ask me: (a) what framework my site is built on (plain HTML, Next.js, Vite, CRA, Vue/Nuxt, Astro, etc.), (b) what events I want to fire and from where (which form, which route, which button), (c) what my Checkout URL pattern should be, (d) which fields on my thank-you page hold the order_id, email, currency, and line items so you can write the tmOrder assignment correctly. - Ask me whether this is a single-checkout-into-one-thank-you-page funnel or a multi-step / upsell funnel (a checkout page, then one or more upsell or downsell pages, each its own charge). For a single thank-you page, use one window.tmOrder on the thank-you page. For a multi-step / upsell funnel, fire Purchase per charge with tmFireEvent("Purchase", { value, currency, order_id }) from each charge-success handler instead - value in DOLLARS, and each charge's own payment id (Stripe pi_..., PayPal capture id) as order_id - so no sale is lost if the buyer leaves before the final thank-you page. Never use both methods for the same order. - Then write the integration: where the snippet goes in my codebase, the exact code for each event call, and where to drop window.tmOrder on the thank-you page. Use the framework-idiomatic mechanism (next/script, react-helmet, useHead, etc.) - do not put raw ``` `YOUR-CNAME` is the first-party hostname you set up at [CNAME setup](/getting-started/cname-setup/) (for example `t.yourdomain.com`). ### 2. Paste the snippet in your site head Paste it just before `` on every page you want tracked. The snippet is `async`, so it loads in the background without blocking your page render. If you use a framework, see [Framework notes](#framework-notes) below for the idiomatic way to inject it. ### 3. Set the Checkout URL pattern (optional) Same install card has a **Checkout URL pattern** field. The tracker watches the page URL and fires `InitiateCheckout` whenever the URL contains the word you configure. Default is `checkout`. Change it to whatever word appears in your checkout-step slug (`order`, `cart`, `payment`, etc.). The match is case-insensitive and substring-based. Setting it to `cart` matches `/cart-page`, `/your-cart`, `/cart-funnel-3`. ## What runs automatically Once the snippet loads, the tracker handles these on its own - you never call any of them manually: - **Pageview** on the initial load and on every SPA route change. - **InitiateCheckout** when the page URL contains your configured Checkout URL pattern. - **First-party session cookie** on the CNAME domain. - **Click-ID capture** on first touch: `utm_*`, `gclid`, `fbclid`, `ttclid`, `wbraid`, `gbraid`. - **Form-field capture** of email, phone, first name, and last name as visitors type, so every subsequent event carries identity for ad-platform match quality even before you call `tmIdentify`. - **Client IP, user agent, timezone** resolved server-side. You only have to write code for events the tracker cannot see on its own: leads, schedules, and purchases. ## The tracker API The snippet exposes exactly three globals. That is the complete public API. ### window.tmFireEvent(name, customData, userData) Fires any event. Standard event names are `Lead`, `InitiateCheckout`, `Schedule`, `Purchase`, `Identify`. Any other name is treated as a custom event and forwarded as-is to the ad platforms that accept custom events. ```js window.tmFireEvent( "Lead", // 1. event name { source: "newsletter_popup" }, // 2. customData - optional context { email: "user@example.com", // 3. userData - PII for ad-platform match first_name: "Jane", last_name: "Doe", phone: "+15551234567" } ); ``` - `name` (string, required) - standard name or your own custom name - `customData` (object, optional) - event-specific context. For `Purchase` this carries `value`, `currency`, `order_id`, `content_ids`, `num_items`, `items[]`. For other events, anything you want to attach. - `userData` (object, optional) - PII for ad-platform match quality. Fields: `email`, `first_name`, `last_name`, `phone`, `city`, `state`, `zip`, `country`, `dob`, `gender`, `externalId`. Hashed server-side before fanout. If you have already called `tmIdentify`, you can pass `null` for `userData` - the tracker remembers the identity from `_tmIdentity` and applies it automatically. ### window.tmIdentify(email, traits) Persists the visitor's identity in `sessionStorage` so every subsequent event in the session carries the same PII bundle to ad-platform fanout - even if you forget to pass `userData` on later `tmFireEvent` calls. ```js window.tmIdentify("user@example.com", { first_name: "Jane", last_name: "Doe", phone: "+15551234567" // Optional: city, state, zip, country, dob, gender, externalId, address }); ``` Call it once when you first know the email (right after opt-in success, login success, or a checkout-step email field is filled). The tracker also auto-stitches identity from the form-field interceptor (see [What runs automatically](#what-runs-automatically)) so explicit calls are belt-and-braces. Calling this early also matters for conversions that reach us by webhook rather than from the browser. An email we have already seen on a visit is what lets a webhook conversion find that person, so capture it as soon as you have it. For the exact way a webhook conversion is matched to a visitor, and the hidden-field snippet that makes it exact, see [Make sure the conversion lands on the right visitor](../../integrations/webhooks/#make-sure-the-conversion-lands-on-the-right-visitor). **Two ways to fire Purchase - pick the one that matches your funnel:** - **One checkout into one thank-you page** (most stores): set `window.tmOrder` on the thank-you page. Simplest - covered below. - **A multi-step or upsell funnel** (a checkout page, then one or more upsell / downsell pages, each its own charge): fire a `Purchase` from your code the moment **each** charge succeeds, with `window.tmFireEvent("Purchase", ...)`. This captures every sale the instant it happens, so a buyer who pays and then leaves before the final thank-you page is still tracked. See [Purchases in upsell funnels](#purchases-in-upsell-funnels). Never use both methods for the same order, or it will be counted twice. ### window.tmOrder = { ... } Set this object on your thank-you / order-confirmation page. The tracker polls `window.tmOrder` every 500ms for up to 30 seconds and the moment it appears, fires `Purchase` + `Identify` with the order's contents. ```js window.tmOrder = { order_id: "ORD-12345", // REQUIRED, unique per order email: "buyer@example.com", // recommended, improves match quality currency: "USD", // optional, defaults to USD items: [ { sku: "SKU-1", // recommended name: "Product A", // recommended price: 4900, // REQUIRED, in CENTS (4900 = $49.00) qty: 1 // optional, defaults to 1 // Optional: category, brand, variant, image_url }, { sku: "SKU-2", name: "Product B", price: 2900, qty: 2 } ] }; ``` The tracker computes the total from `price * qty` summed across items (divided by 100 to get the dollar value), dedups by `order_id` in `sessionStorage`, and fires once. Refreshes of the thank-you page do not double-fire. Each `items[].price` here is in cents. If instead you fire Purchase directly with `window.tmFireEvent("Purchase", { value })` (see [Purchases in upsell funnels](#purchases-in-upsell-funnels)), that `value` is in dollars, not cents. **Same-page upsells.** If your checkout offers post-purchase upsells on the same page, mutate `window.tmOrder.order_id` and `window.tmOrder.items` after each upsell completes. The tracker keeps polling and fires once per unique `order_id`. ```js // First charge succeeded window.tmOrder = { order_id: "ORD-12345", email: "...", items: [{ ... }] }; // User accepted upsell - new order fires window.tmOrder.order_id = "ORD-12345-UP1"; window.tmOrder.items = [{ sku: "UPSELL-1", name: "Bonus Pack", price: 1900, qty: 1 }]; ``` ## Purchases in upsell funnels If your funnel charges in steps - a main checkout, then post-purchase upsells or downsells, each on its own page or as its own charge - do not wait for the thank-you page to report the sale. Fire a `Purchase` from your charge-success handler the moment each charge clears, so no sale is lost if the buyer drops off mid-funnel. Call `window.tmFireEvent("Purchase", ...)` at every successful charge - the main checkout and each upsell - using that charge's own payment id as `order_id`: ```js // Run this the instant a charge succeeds (main checkout, each upsell, each downsell): window.tmFireEvent("Purchase", { value: 47.00, // DOLLARS - this charge's amount (NOT cents) currency: "USD", order_id: paymentId // unique per charge, e.g. the Stripe pi_... id }, { email: buyerEmail } // pass first_name / last_name / phone too if you have them ); ``` - `value` is in **dollars** here (`47.00`) - unlike `window.tmOrder.items[].price`, which is in cents. This is the one place the two purchase methods differ. - `order_id` must be **unique per charge**. Use your payment processor's transaction id (Stripe `pi_...`, PayPal capture id). The tracker dedups on it, so a refresh or retry will not double-count, and each charge is recorded as its own sale. - Fire it the moment success is confirmed, before or together with any redirect to the next step - the event is sent with a keepalive request, so it survives the page change. Example - a checkout handler and an upsell handler: ```js // Main checkout charge succeeded function onCheckoutPaid(charge) { window.tmFireEvent("Purchase", { value: 97.00, currency: "USD", order_id: charge.id }, { email: customer.email, first_name: customer.firstName }); // ...then send them to the first upsell } // Upsell accepted and charged function onUpsellPaid(charge) { window.tmFireEvent("Purchase", { value: 297.00, currency: "USD", order_id: charge.id }, { email: customer.email }); // ...then send them to the next step } ``` **Do not also set `window.tmOrder` for these orders** - per-charge firing replaces it. Using both reports each sale twice. ## Standard events | Event name | When to fire | Who fires it | |---|---|---| | `PageView` | Every page load and SPA route change | Automatic | | `InitiateCheckout` | Visitor reaches your checkout step | Automatic (URL pattern) | | `Lead` | Visitor submits an opt-in form | You, from your form success handler | | `Schedule` | Visitor confirms a calendar booking | You, from your booking success handler | | `CompleteRegistration` | Visitor completes a signup or registration | You, from your signup success handler | | `Purchase` | Order is successfully paid | Automatic via `window.tmOrder`, or you, per charge via `tmFireEvent` in upsell funnels | | `Identify` | First time you know the visitor's email | Automatic (via `window.tmIdentify` or form interceptor) | ### Lead Fired from your opt-in form's success handler. Pass user data so the ad platforms can match the lead to a known person: ```js window.tmFireEvent("Lead", { source: "homepage_hero_form" }, { email: "lead@example.com", first_name: "Jane", last_name: "Doe", phone: "+15551234567" }); ``` ### Schedule Fired from your booking-confirmation handler (Calendly redirect handler, custom calendar webhook, etc.): ```js window.tmFireEvent("Schedule", { appointment_id: "abc-123" }, { email: "booker@example.com", first_name: "Jane", last_name: "Doe", phone: "+15551234567" }); ``` ### CompleteRegistration Fired when someone finishes a signup: an account is created, a membership form is submitted, a free trial is started, a course enrolment completes. Call it from the handler that runs once the registration has actually succeeded, not on form submit. ```js window.tmFireEvent("CompleteRegistration", { content_name: "Free account", // optional - which signup this was status: true, // optional - registration completed value: 0, // optional - what a signup is worth to you currency: "USD" // optional - required only if you send value }, { email: "new.user@example.com", first_name: "Jane", last_name: "Doe", phone: "+15551234567" }); ``` Every field in the first object is optional, including `value` and `currency`. Send `value` only if a signup has a meaningful worth to you and you want Facebook to optimise toward it; if you do send it, send `currency` with it. `status` is a boolean meaning the registration completed, and `content_name` labels which signup it was when you have more than one. The second object is the same identity bundle as `Lead`, and it is the half that matters for matching. The tracker adds the rest on its own: the Facebook click id (`_fbc`) and browser id (`_fbp`), the visitor's IP and user agent, and the TrueMetriks visitor id, all on top of whatever email, name and phone you pass. Everything personal is hashed before it reaches an ad platform. If you already called `tmIdentify`, the stored identity is merged in automatically and you can leave the third argument out. **There is nothing to switch on.** `CompleteRegistration` is not one of the seven events every install gets, because most platforms cannot send it. So it stays out of the way until your site fires one, and then it simply works: the event is forwarded to your connected pixels from the first fire, and a new checkbox appears under **Settings > Integrations > Facebook > Allowed events** already ticked, so you can see it and switch it off if you ever want to. Firing it from your code is the whole setup. ### Custom events Any string is a valid event name. Use this for product-specific events your funnel cares about: ```js window.tmFireEvent("WatchedVSL", { length_seconds: 180 }); window.tmFireEvent("AddedToWishlist", { sku: "SKU-1" }); window.tmFireEvent("DownloadedWhitepaper", { paper_id: "roas-2026" }); ``` ## Framework notes Same API, different placement per framework. Pick yours. ### Plain HTML / Webflow / static site Snippet goes in the page `` (or your site builder's site-wide "head" / "custom code" / "tracking" field). ```html ``` Substitute the templating syntax for whatever your stack uses (Jinja, Liquid, ERB, EJS, Handlebars). ### React (Next.js) Inject the snippet through `next/script` in your root layout. Call the API from `useEffect` or event handlers. ```tsx // app/layout.tsx import Script from "next/script"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( ``` Everything else (firing events in handlers, setting `window.tmOrder` in `useEffect`) is identical to the Next.js example above. ### Vue (Nuxt 3) ```ts // nuxt.config.ts export default defineNuxtConfig({ app: { head: { script: [ { src: "https://YOUR-CNAME/t/s-custom.js", async: true } ] } } }); ``` ```vue ``` ### Astro / Svelte / Solid / other If your framework lets you put a ` ``` Your payload then carries one extra line: ```json { "email": "jane@example.com", "value": 49.99, "currency": "USD", "tm_evid": "ev_k3f9q2_a1b2c3d4" } ``` **Read the cookie in the browser. Never bake it into the page.** If a CDN caches the page holding your hidden field, one visitor's `_evid` gets saved into the HTML and handed to everybody who loads it after them, and all of their conversions pile onto that one person. We trust the visitor id above everything else, so nothing later catches the mistake. The snippet above is safe because it reads the cookie as the form is sent, in that visitor's own browser. #### Send the click id if you have it `fbclid` and `gclid` sit in the landing-page URL, so you can put them in a hidden field the same way, with no cookie to read. Another option when the visitor id is awkward to get at. You can name the field any of these: `fbclid`, `gclid`, `click_id`, `tm_click_id`. #### What we try, in order - **Visitor id** - exact, no guessing. - **Click id** - `fbclid` or `gclid`, matched against the click ids we stored for that visitor. - **Email** - matched against the email attached to their visits. We stop at the first one that finds a real visit. For most sites the email on its own gets there, which is why nothing above is required. Sending more than one is safe either way - they do not clash, the strongest one wins. #### Why a conversion shows no visit A conversion with no visit attached means nothing in that payload matched a visitor we had seen. It still counts and the money is still yours, but with no journey behind it there is no ad to credit. Adding the visitor id fixes it from then on. It cannot go back and fix conversions that already arrived, so the sooner it is in, the sooner your attribution fills in. ### Also fire to connected ad platforms Each receiving webhook has an **Also fire to connected ad platforms** toggle, off by default. Turn it on and the events also go to Facebook, TikTok, GA4 and Google Ads, just like a native integration. Your analytics get them either way. Custom events work too: `Call Booked` reaches your ad platforms under that same name. #### Send a custom event to Facebook as a Custom Conversion Once a custom-named receiving webhook exists, its event name shows up in Facebook's **Create custom event** dialog, under a **Custom webhook events** group. Pick it there and we create a Facebook Custom Conversion with that name, so Facebook can optimize and report on it. We store it as an `OTHER`-type conversion, so it is never mistaken for a Lead or a Purchase, and it fires once per event with no duplicates. Facebook only gets the event while that webhook's **Also fire to connected ad platforms** toggle is on. See the [Facebook integration guide](../../ad-integrations/facebook/) for the dialog. ### Duplicate protection We count each conversion once, even when two tools report it: - **Leads, Schedules and custom events:** the same email and event type counts once per day, across every source. A webhook and a native integration (say [Typeform](../../form-integrations/typeform/)) reporting the same lead will not double-count. - **Purchases** are checked on email, order and amount instead, so two genuinely different orders on the same day both count. If a native integration already reports the same event type for this site, the webhook shows an amber warning as a reminder. ### Recent deliveries Every receiving webhook keeps a **Recent deliveries** log: the last 25 receipts, 5 per page. Each one shows what happened (`accepted`, `deduped`, `parse error`, `oversized` or `ignored`) plus a preview of the payload, so you can see exactly what arrived. ### Rotating or removing a webhook There is no regenerate button, on purpose. To change a URL, delete the webhook and create a new one, which gets a new URL. Deleting one stops its URL working straight away. Payloads over 256 KB are rejected. ## Send webhooks Push conversions out of TrueMetriks to any URL - a CRM, GoHighLevel, or your own backend. Every Lead, Schedule and Purchase goes to your URL with the person's first and last touch, sessions and paid channel attached. Leads reach GoHighLevel with the whole journey on them. ### Step 1 - Create a sending webhook Switch to the **Send webhooks** tab and click **Add sending webhook**. Fill in the dialog: - **Name** - a label for you (for example "GoHighLevel contacts"). - **Webhook URL** - where we POST the payload. Must be `https`. - **Send this event** - pick ONE event from the dropdown: **Lead**, **Schedule**, **Purchase**, or one of **your custom events** (your Facebook Custom Conversions and custom-named receiving webhooks). One sending webhook sends one event. To send another event to the same place, create a second one. Click **Create webhook**. ### Step 2 - Copy the signing secret We show a **signing secret** once, right after you create the webhook. Copy it now, you will not see it again. Every delivery is signed so your receiver can check it came from us. The signature rides in an `X-TrueMetriks-Signature: t=,v1=` header, built from the request body and your signing secret. Checking it is optional - receivers that do not check signatures (GoHighLevel inbound webhook triggers, for example) can ignore the header. ### Step 3 - Know the timing A delivery fires about **75 seconds** after the event arrives. The wait is on purpose: identity and attribution settle first, so the payload carries the full journey. To test the wiring without waiting, click **Send test** and a sample payload arrives in about 15 to 30 seconds. The test uses this webhook's real event name (`Call Booked`, not a generic `Test`), so your receiver sees exactly what a live delivery looks like. It is still marked with `"test": true` at the top level (and `event.properties.test: true`) so nothing downstream counts it as a real conversion, and in the **Recent deliveries** log the row reads `Test: Call Booked`. On a test send `contact` is null and `attribution` is sample data. ### The payload Every delivery is a JSON POST. `event.type` is `Lead`, `Schedule` or `Purchase` for standard events, and `Custom` for your own, so your receiver can branch on it: ```json { "api_version": "2026-07-09", "webhook": { "destination_id": "wdst_...", "attempt": 1, "sent_at": "2026-07-11T10:17:27.718Z" }, "site": { "id": 1, "domain": "example.com" }, "event": { "type": "Purchase", "name": "Purchase", "id": "evt_...", "time": "...", "value": 49.99, "currency": "USD", "source": "webhook", "properties": {} }, "contact": { "email": "jane@example.com", "phone": null, "first_name": "Jane", "last_name": null }, "attribution": { "first_touch": { "source": "google", "medium": "cpc", "campaign": "brand-search", "term": "brand-exact", "content": "headline-a", "channel": "Paid Search", "referrer": "https://www.google.com/", "landing_url": "example.com/landing", "campaign_name": "brand-search", "adset_name": "brand-exact", "ad_name": "headline-a", "campaign_id": null, "adset_id": null, "ad_id": null }, "last_touch": { "source": "facebook", "medium": "paid", "campaign": "retargeting", "term": "120210000000000001", "content": "video-testimonial-v3", "channel": "Paid Social", "referrer": "https://facebook.com/", "landing_url": "example.com/offer", "campaign_name": "retargeting", "adset_name": "Warm audience - 30d", "ad_name": "video-testimonial-v3", "campaign_id": null, "adset_id": "120210000000000001", "ad_id": "120210000000000002" }, "touches": 7, "sessions": 3, "days_to_convert": 7, "paid": true, "paid_channel": "Paid Social" } } ``` On real events `contact` carries the person's actual email, phone, and name, and `attribution` their real journey. On test sends `contact` is null and `attribution` is sample data. #### Which campaign, which ad set, which ad Each touch answers that three times over, so you can map it whichever way your CRM wants: - **`campaign_name`, `adset_name`, `ad_name`** are the readable answer. Use these. They are the same names the TrueMetriks dashboard shows for that visit. - **`campaign_id`, `adset_id`, `ad_id`** are the platform's own IDs for the same three things, for joining back to Facebook, Google or TikTok. `null` when the visit did not carry one. - **`source`, `medium`, `campaign`, `term`, `content`** are the raw UTM parameters exactly as they arrived, nothing resolved or cleaned. Use these if you tag with your own convention and would rather map it yourself. A `*_name` is `null` when nothing readable exists for that level - the ad platform sent only an ID and we have no connected account to look the name up in. The matching `*_id` is then your handle on it. We would rather tell you nothing than print an ID where a name belongs. A custom event delivers the same shape, with `event.type` set to `Custom` and `event.name` set to your event name: ```json { "api_version": "2026-07-09", "webhook": { "destination_id": "wdst_...", "attempt": 1, "sent_at": "2026-07-17T10:17:27.718Z" }, "site": { "id": 1, "domain": "example.com" }, "event": { "type": "Custom", "name": "Call Booked", "id": "evt_...", "time": "...", "source": "webhook", "properties": {} }, "contact": { "email": "jane@example.com", "phone": null, "first_name": "Jane", "last_name": null }, "attribution": { "first_touch": { "source": "google", "medium": "cpc", "campaign": "brand-search", "channel": "Paid Search", "campaign_name": "brand-search", "adset_name": "brand-exact", "ad_name": "headline-a" }, "last_touch": { "source": "facebook", "medium": "paid", "campaign": "retargeting", "channel": "Paid Social", "campaign_name": "retargeting", "adset_name": "Warm audience - 30d", "ad_name": "video-testimonial-v3" }, "touches": 4, "sessions": 2, "days_to_convert": 1, "paid": true, "paid_channel": "Paid Social" } } ``` A **test** delivery of that same webhook looks identical but adds a top-level `"test": true` and `event.properties.test: true`, so your receiver can drop it. ### Reliability - **Stable IDs, so you can drop repeats.** Each delivery carries an `X-TrueMetriks-Delivery: dlv_` header and an `event.id` field, so your receiver can spot a retry. The `X-TrueMetriks-Event` header carries the event name. - **Automatic retries**, spaced further apart each time, when a delivery fails. - **Dead URLs pause themselves.** A URL that keeps failing is paused for you. The card shows why, with an **Unpause** button once your receiver is fixed. - **Recent deliveries** shows every attempt (`delivered`, `queued`, `sending` or `failed`) with the HTTP code. ## Related - Send conversions into GoHighLevel: [GoHighLevel install guide](../../platform-installs/gohighlevel/) - Fire conversions from your own site: [Custom install guide](../../platform-installs/custom/) - Capture the email on your site so webhook conversions match: [window.tmIdentify](../../platform-installs/custom/#windowtmidentifyemail-traits) - Forward conversions to your ad platforms: [Facebook CAPI](../../ad-integrations/facebook/), [Google Ads](../../ad-integrations/google-ads/), [GA4](../../ad-integrations/ga4/), [TikTok](../../ad-integrations/tiktok/) # Microsoft Clarity Source: https://docs.truemetriks.com/session-replay/clarity/ Add free Microsoft Clarity session replay from TrueMetriks by pasting one Project ID, no code, and tag every recording with the event name to filter by outcome. ## What you need A Microsoft Clarity account with a project for this website. Clarity is free at [clarity.microsoft.com](https://clarity.microsoft.com) - sign in with a Microsoft, Google, or Facebook account and create a project that points at your site's URL. Each project has a short **Project ID**, which is the only thing you paste into TrueMetriks. ## Connect Microsoft Clarity to TrueMetriks ### Step 1 - Open Settings In TrueMetriks, pick the site you want from the selector at the top of the left sidebar, then click **Settings** at the bottom of the sidebar. ### Step 2 - Open the Microsoft Clarity tile Click the **Integrations** tab, then click the **Microsoft Clarity** tile. ### Step 3 - Copy your Clarity Project ID In Clarity at clarity.microsoft.com, open your project and go to its **Settings**. The **Project ID** sits at the top of the **Overview** - click the copy icon next to it. It is a short string like `qz8mn4k2p`. The Project ID is also the trailing part of your Clarity tag URL: in **Settings -> Setup** the install snippet loads `https://www.clarity.ms/tag/` - the bit after `/tag/` is the same ID. ### Step 4 - Paste the ID and save Back in TrueMetriks, paste the ID into **Clarity Project ID**, leave **Tag Clarity sessions with TrueMetriks event names** ticked, and click **Save**. The tile now reads **Configured**. Clarity starts loading on every page on the next visit, and recordings appear in your Clarity dashboard within a few minutes. ## Tag Clarity sessions with TrueMetriks event names This is the checkbox under the Project ID. Leave it on (the default) and each recording is labelled with the TrueMetriks event that fired, so in Clarity you can **filter recordings by that tag** and jump straight to, say, every session that ended in a Purchase. The tag uses the same event names you see on the [Events page](../../dashboard/events/) and the standard conversion events from your [Facebook](../../ad-integrations/facebook/) and other integrations. ## Load more than one project A single site can feed more than one Clarity project. Click **+ Add project**, paste another Project ID, and Save - TrueMetriks loads every project you list on each page, and each keeps its own **Tag sessions** toggle. This is useful when two teams keep separate Clarity workspaces, or when you are migrating from an old project to a new one and want both recording for a while. To remove a project, clear its ID (or use the trash icon when more than one row is shown) and Save. ## Where your recordings show up Recordings and heatmaps live in **Clarity**, not in TrueMetriks. Open clarity.microsoft.com, pick this project, and you will see the session list, recordings, and heatmaps there. ## If sessions are not showing up - **Give it a few minutes.** Clarity batches recordings; the first sessions can take a little while to appear after you save the Project ID. - **Confirm the TrueMetriks tracker is live on the page.** Clarity rides your TrueMetriks tracker, so if a page is not sending visits to TrueMetriks it will not load Clarity either. Check that the page shows up in your TrueMetriks [visits](../../dashboard/sessions/) first, and see the [install guide](../../getting-started/install-first-tracker/) if it does not. - **Check the Project ID.** It is the short string after `/tag/` in the Clarity snippet - no spaces, no full URL. Re-copy it from Clarity's Overview and Save again. - **Confirm the project is for this site** in Clarity (the Website URL on the Overview screen should match), or recordings will land in the wrong project. # Main Source: https://docs.truemetriks.com/dashboard/main/ The Main dashboard is your at-a-glance overview: traffic and revenue stat cards, a trend chart, and breakdown panels for referrers, pages, tech, and geography. ## The overview cards The row of cards at the top is your headline summary for the selected period. Each card shows the metric, its percent change versus the previous period of equal length (green means it moved the good way, red means worse), and a small sparkline of the trend. The traffic cards: - **Unique Users** - how many distinct people visited. - **Sessions** - how many visits (a session ends after 30 minutes of inactivity). - **Pageviews** - total pages loaded across all visits. - **Pages per Session** - average number of pages viewed per visit. - **Bounce Rate** - share of visits that left after a single page. - **Session Duration** - average length of a visit. The commerce cards: - **Purchases** - number of completed purchase events. - **Revenue** - total sales value in the period. - **AOV** - average order value (revenue divided by purchases). - **Conversion Rate** - share of sessions that ended in a purchase. - **New Buyers** - buyers making their first-ever purchase. - **Returning Buyers** - buyers who had purchased before. ## The trend chart The chart plots one metric over time. The solid line is the selected period and the grey line is the previous period of equal length, so you can see whether you are up or down. Use the **Day** control to change the grain (hour, day, week, month), and hover any point to read the exact value. ## Referrers and Pages These two panels sit side by side and answer "where did traffic come from" and "what did it land on." **Referrers** ranks the sites that sent you traffic. The **Channels** tab groups those sources (Paid Social, Organic Search, Direct, and so on), and the **UTM** tab breaks traffic down by your [campaign tags](../utm-builder/). **Pages** ranks your most-visited URLs. The **Titles** tab lists the same visits by page title instead of path, and **Entries** and **Exits** show which pages visits start and end on. ## Browsers and Countries **Browsers** shows what your visitors browse with. The **Devices**, **Operating Systems**, and **Screen Dimensions** tabs answer the same "what are they using" question for hardware and software. **Countries** shows where your visitors are. The **Regions**, **Cities**, **Languages**, **Map**, and **Timezones** tabs zoom in on the same geography from different angles. ## Events and Weekly Trends **Events** counts how often each tracked action fired - Lead, Purchase, View Product, and any other [event you send](../events/). The **Outbound Links** tab counts clicks that left your site. **Weekly Trends** is a heatmap of activity by hour and day of week - darker cells are busier times. Switch the metric in the selector to find when your audience is most active. # Revenue Source: https://docs.truemetriks.com/dashboard/revenue/ The Revenue page shows server-side revenue, ROAS, and profit by source, drilled down to the individual ad, plus a per-buyer table of who purchased. **Video guide** ## What the Revenue page shows The page has a row of headline cards, a set of attribution and audience controls, a breakdown table you can drill from channel all the way down to the exact ad, and a duration block that shows how long buyers take to convert. Everything respects the date range at the top. ## The top cards - **Total Revenue** - all purchase value attributed to this period. - **Purchases** - how many purchases happened. - **AOV** - average order value (Total Revenue divided by Purchases). - **LTV** - average lifetime value per buyer, so you can compare what a customer is worth against what you pay to acquire one. ## Attribution and audience controls These controls decide how the numbers below are calculated. Change one and the whole table recomputes. - **Attribution** - which touch gets the credit for a conversion: **First-touch** (the source that first brought the visitor in), **Last-touch** (the most recent source before the purchase), or a linear split. This is how you settle "which channel gets the sale." - **Audience** - whether to count **New + Returning**, only new, or only returning buyers. - **Conversion events** - which events count as a conversion here. Lead and Purchase are added by default; use **+ Add event** to score on any other event you send. - **Columns** - choose which metric columns the table shows. ## The breakdown table This is the heart of the page. The tabs across the top - **Source**, **Medium**, **Campaign**, **Content**, **Term** - group the rows by the matching [UTM dimension](../utm-builder/). Each row carries the full funnel: - **Visits** and **Unique** - traffic volume. - **Lead** and **Purchase** - conversions of each type. - **Revenue** - sales attributed to that row. - **Spend** - ad spend we pulled from the connected ad platforms ([Facebook](../../ad-integrations/facebook/), [Google](../../ad-integrations/google-ads/)). - **ROAS** - revenue divided by spend. Green is profitable, red is below breakeven. - **Profit** - revenue minus spend. Expand a row with the chevron to drill down a level at a time - **source -> medium (paid or organic) -> campaign -> ad set -> ad** - so you can see the exact ad that made or lost money, not just the channel total. Paid rows are labelled by level with a small `CAMPAIGN`, `AD SET`, or `AD` tag (Google and TikTok read `AD GROUP` instead of `AD SET`) so you always know how deep you are. Traffic with no campaign tags rolls up into `(Direct)` (no tags and no referrer) and `(Untagged)` (a referrer but missing UTM tags), so nothing goes uncounted. Green rows are winning, red rows are bleeding spend. Use **Search** to find a source, the row count to see how many groups matched, and **Export CSV** to pull whatever is on screen. The **Total** row at the bottom sums everything in view. ## Average first-conversion duration Below the breakdown table is the duration block: it tells you how long it typically takes for someone to convert after they first find you, so you can see which channels close fast and which run a long sales cycle. Read the **median** as the headline number and the lighter **avg** beneath it as the average. The median is the outlier-safe one to trust, since a single buyer who took months to convert can drag the average up without moving the median. Each buyer contributes exactly one data point: the time from their **first tracked visit** (their first click) to their **first conversion** of the selected type. A customer who buys again next month is still one point, measured to that first purchase, so this is your true first-conversion cycle, not a repeat-purchase gap. Some conversions never had a preceding click - imported, server-side, or webhook events whose first tracked event *is* the conversion. Those are excluded from the duration math, which is why a row can show sales but a `-` in the duration column. Durations under a minute display as `<1m`. The segmented toggle at the top switches the conversion type: **Purchase**, **Lead**, or **Book a Call**. "Book a Call" counts the standard Schedule event plus any custom event you have mapped to Schedule (Calendly, GHL, webhooks). The count column relabels itself to match - **Total Sales**, **Total Leads**, or **Total Booked Calls**. The **Platforms**, **Campaigns**, **Ad Sets**, and **Ads** tabs choose how the rows are grouped. On **Platforms** each ad platform splits into paid and organic rows - **Facebook Ads** versus **Facebook Organic**, and the same for Google and TikTok. Everything you bought through Google Ads sits under the **Google Ads** row and breaks out by campaign, ad set, and ad in the deeper tabs; the standalone **YouTube** row is organic YouTube traffic, not Google Ads video. Each row carries four columns: **Name**, **Total {Sales / Leads / Booked Calls}**, **Median Duration** (bold median plus avg beneath), and **ROAS** (green at 1x or above, red below, `-` where there is no spend to compare against). A thin magnitude bar sits under each number, scaled to that column's own largest value - count bars are blue, duration bars orange, and ROAS bars green or red - so you can eyeball the leaders down a column at a glance. Vertical separators divide the columns, and the bar-less **Total** row at the bottom rolls up everything in view. A `-` anywhere means there are no attributable conversions at that level (organic platforms have no ad set or ad rows). The block honors the same date range, attribution (first, last, or any touch), audience (new, returning, or both), and traffic (all, paid, or organic) controls as the breakdown table above, so changing a control recomputes the durations the same way it recomputes the table. ## Buyers Below the breakdown is the per-buyer table - one row per customer who purchased: - **Buyer** - the customer's email, or an anonymous id if we never captured one. - **First touch** and **Last touch** - the source that first and last brought them in. - **Sessions** - how many visits they took before buying. - **Revenue** - how much they have spent in total. - **Days first to purchase** - how long from their first visit to their purchase, which tells you how long your sales cycle really is. Use the **Prev** and **Next** controls to page through the full list. # How revenue attribution works (and how to reconcile with Facebook, Google and TikTok) Source: https://docs.truemetriks.com/dashboard/revenue-attribution/ Learn how first-touch, last-touch, and any-touch attribution work, why a sale can show under a different channel than Facebook or Google, and how to match them up. If a sale shows up under a different channel in TrueMetriks than it does in Facebook or Google, nothing is broken. The two are answering different questions, and each is right about its own question. Once you see which question each one asks, the numbers stop looking like they disagree. This page covers the three attribution models, why the date range changes the answer, and how to compare TrueMetriks against an ad platform. ## What are the three attribution models? The **Attribution** dropdown on the [Revenue](../revenue/) page decides which channel gets credit for a sale. Pick a model and the whole table recalculates. - **First-touch** gives the credit to the channel that first brought the person in. Use it to see what starts journeys. - **Last-touch** gives the credit to the channel on the visit where the sale happened. This is the default. Use it to see what closes sales. - **Any-touch** gives credit to every channel the person touched on the way to buying. If someone touched Facebook, then Google, then email before buying, all three get credit for that one sale. Any-touch credits one sale to more than one channel, so the per-channel numbers can add up to more than your real sales count. That is normal. The channel columns can add up to more than the **Total** row, but the Total row still shows the true number of sales. No revenue is counted twice; one sale is just shown in every channel that helped. For a deeper look at the models and how to read one buyer's path, see [Buyer journeys and attribution models](../../features/buyer-journeys-attribution/). ## Why does a sale show under a different channel than Facebook or Google? The table only counts the touches inside the date range you picked. That is on purpose. It answers "in this range, which channels drove sales," not "across all time." Change the range and you change which touches are in view, which can change which channel gets the last-touch credit. Here is the example that confuses people. Someone: 1. Clicks a **Facebook** ad on day 1. 2. Clicks a **Google** ad on day 2. 3. Opens an **email** and buys on day 3. Look at day 3 on its own with last-touch and TrueMetriks credits **Email**, because email is the only touch in that one day and it is the visit where they bought. At the same time, Facebook still counts the sale, because it looks back several days from the ad click and sees its day-1 click. Google does the same thing with its own click on day 2. Every ad platform works this way: it looks back over its own window and claims any sale that followed one of its clicks. So the same sale can honestly show up in TrueMetriks under Email, in Facebook, and in Google all at once. To see the full picture in TrueMetriks, widen the date range to cover day 1 through day 3 and switch to any-touch. Now Facebook, Google, and Email all show credit for that buyer, which matches what each ad platform tells you. ## What is the difference between Purchases and Buyers? These two columns count different things, and it is easy to mix them up. - **Purchases** counts orders. One person who orders three times is three purchases. - **Buyers** counts people. That same person is one buyer, no matter how many times they ordered. Use **Buyers** when you want to know how many customers you won, and **Purchases** when you want to know how many orders came in. When you compare against an ad platform, use **Purchases**, because ad platforms count conversions (orders), not unique people. ## How do I compare TrueMetriks with an ad platform? To check TrueMetriks against Facebook, Google, or TikTok for a period: 1. Set the date range to the period you want to compare. 2. Switch **Attribution** to **Any-touch**. This credits every channel that helped, which is the closest match to how ad platforms count. 3. Filter the **Channel** to the exact platform, for example `Facebook Paid`, `Google Paid`, or `TikTok Paid`. Pick the exact platform, not a broad group. Choosing **Paid Social** also pulls in Google and TikTok, so the number comes out too high to compare against a single ad manager. `Facebook Paid` shows only the traffic you can line up against Facebook, `Google Paid` against Google Ads, and `TikTok Paid` against TikTok. See the [Facebook](../../ad-integrations/facebook/), [Google Ads](../../ad-integrations/google-ads/), and [TikTok](../../ad-integrations/tiktok/) guides for how that data arrives. ## Why won't the numbers match 100%? Aim for about 95%, not a perfect match. A few honest gaps sit between any two tools, and it helps to know them so you do not chase a number that cannot exist. - **Sales from people who only saw the ad.** Facebook and Google both count some sales from people who saw an ad but never clicked it. A tool that measures clicks cannot see a view that was never clicked, so the ad platform will always claim a few sales TrueMetriks gives to another channel. - **Clicks from before you installed the tracker.** A new tracker only knows about clicks that happened after it went live. Sales from earlier clicks look unattributed until enough time passes. - **Blocked signals.** Safari, ad blockers, and privacy settings hide a small share of clicks on every platform. This is a few percent, not most of your traffic. None of these mean a tool is broken. They are why two correct tools land close to each other instead of exactly on top of each other. Use last-touch to see what closes sales, any-touch to see every channel that helped, and the steps above when you need to compare a period against Facebook, Google, or TikTok. # Leads Source: https://docs.truemetriks.com/dashboard/leads/ The Leads page is your events explorer: every event you fire ranked by volume, with a detail view showing each event's conversion rate and the paths to it. **Video guide** ## The events list The page opens on a searchable list of every event, ranked by count. Each row shows: - **Event** - the event name (your built-in and custom events). - **Count** - how many times it fired in the selected period, with a sparkline of its trend. - **Conversion Rate** - the share of visits that fired this event at least once. - **Revenue** - total revenue tied to the event (only money-carrying events like Purchase show a value; the rest show a dash). Click any event to open its detail. ## The event detail Opening an event (here, **Lead**) shows its headline stats, a conversion comparison, and the paths that led to it. ### The headline stats - **Total Visits** - visits in the period that could have fired this event (the denominator for conversion rate). - **Total Events** - how many times the event fired in total, counting repeats. - **Unique Events** - how many distinct visitors fired it at least once (repeats by the same person collapse to one). - **Conversion Rate** - Unique Events divided by Total Visits - the share of visitors who did this. - **Revenue** - revenue attributed to the event, when it carries money. ### Converts to **Converts to** lets you measure the drop-off from this event to a later one. Pick a downstream event with **Add target event** - for example, choose Purchase while looking at Lead - and TrueMetriks shows what share of the people who fired this event went on to fire that one. It is how you answer "how many of my leads actually buy." Each target you add gets a row: - **Users Converted** - how many of the people who fired this event later fired the target. In the example, 870 of the Lead visitors went on to Purchase. - **Conv. Rate** - that count as a share of this event's unique visitors. Here 44.16% of leads became buyers. - **Median Time** - the typical gap between the two events, so you can see how long your sales cycle really is (3 days here). - **P90 Time** - the slowest 10% take at least this long (6 days 4 hours), which tells you how long to keep nurturing before a lead is cold. You can add more than one target to compare several downstream events side by side. ### Upstream paths **Upstream paths** is the most useful part: it shows the routes visitors took before the event fired, so you can see *what led to those conversions*. Each row is a path - the source, then the pages, ending in the event firing - with how many visits took it and its share of the total. - **Granularity** controls how much source detail each step shows - **Source + Medium + Campaign** is the most detailed; you can collapse it to just source. - **View** switches between **Same session** (only the steps inside the one visit where the event fired) and **Full user journey** (the visitor's complete history across every visit, including earlier touchpoints). Full user journey uses the same journey logic as [Buyer journeys and attribution](../../features/buyer-journeys-attribution/). For example, the top Lead path above - `(Direct) -> /book -> Lead fired` with 141 visits (57.5%) - tells you most leads come in directly and submit on `/book`, while the rows below show how much each paid source contributes. ## Example: Purchase Every event works the same way, so the same view answers revenue questions too. Open **Purchase** and the Revenue stat is populated, and the upstream paths show exactly which sources and pages led to a sale. This is how you trace revenue back to its origin: if `facebook - cpc - 120213945499580764 -> /checkout/book -> Purchase fired` drives real purchases, you know that campaign is doing the work - not just generating cheap leads. # Pages Source: https://docs.truemetriks.com/dashboard/pages/ The Pages page lists every URL on your site by traffic, and lets you click any page to see its upstream paths: the sources and pages visitors came from. **Video guide** ## The pages list The page opens on a searchable list of every URL on your site, ranked by sessions. Each row shows: - **The page** - its URL path and the page title, with a link icon to open the live page. - **Sparkline** - the page's traffic trend over the selected period. - **Sessions** - how many sessions visited the page. - **Avg time** - the average time visitors spent on it. Use the search box to find a page by URL or title. Click any row to open that page's detail. ## The page detail Opening a page shows its headline stats and, below them, the upstream paths that led to it. ### The headline stats - **Visits** - how many visits the page received in the period. - **% of Total** - the page's share of all visits across the site. - **Avg Time** - the average time on the page. ### Upstream paths **Upstream paths** is the key part. It traces backwards from the page to show *how visitors got there* - each row is the route they took, ending at this page, with how many visits took it and its share of the total. It is the same idea as the upstream paths on the [Leads page](../leads/), but anchored on a page instead of an event. - A row like `(Direct) -> /book` with 503 visits (55.1%) means most people land on `/book` directly, while the rows below show how much each paid source and campaign contributes. - A longer row like `(Direct) -> /book-a-call -> /book` means those visitors hit `/book-a-call` first and then moved to `/book` in the same journey - so you can see the internal routes people take through your site, not just the entry source. - **Granularity** controls how much source detail each step shows. **Source + Medium + Campaign** is the most detailed; collapse it to just source for a higher-level view. This is how you answer "where does the traffic to this page actually come from" - whether a landing page is fed by ads, by organic search, or by other pages on your own site. # Funnels Source: https://docs.truemetriks.com/dashboard/funnels/ The Funnels page builds multi-step funnels from pages and events, shows conversion and drop-off at each step, and split-tests variants head to head. **Video guide** ## Creating a funnel A funnel is an ordered list of steps you define yourself. Click **New funnel**, give it a name, and add your steps. Each step is one of two types - pick it from the dropdown: - **Path** - a page on your site, matched by its URL (for example `/` or `/checkout/book`). - **Event** - an [event you send](../events/) (for example `Lead` or `Purchase`), matched by name. A few things matter when you build it: - **Order is everything.** The steps run top to bottom, and a visitor only counts at a step if they reached every step above it first. Put your steps in the real order people move through them. As the in-app tip says, put the checkout *page* step **before** the purchase *event* step, because the Purchase event only fires after the checkout button is clicked. - **Mix paths and events freely.** A funnel can go page, then event, then page again - for example Homepage (path) then Lead (event) then /checkout/book (path) then Purchase (event). - **Wildcards.** Use `*` to match a single path segment (`/blog/*`) or `**` to match many (`/docs/**/intro`), so one step can cover a whole family of URLs. - Drag the handle to reorder steps, use **+ Add Step** to add more, and click **Save Funnel** when done. ## Reading the funnel Once saved, the funnel shows three headline numbers and then every step with its conversion. The top cards summarise the whole funnel: - **Total Funnel Revenue** - revenue from everyone who completed the funnel, with the order count. - **AOV** - average order value across those orders (revenue / orders). - **Revenue per completed-funnel user** - revenue divided by the number of people who made it all the way through. Then each step shows: - **Visits or events** and **unique visitors** that reached the step, plus how many **dropped** (in orange) since the previous step. - A bar with the conversion percentage. The **solid** green bar is the **overall conversion** - the share of people from the very first step who reached here. The lighter **striped** bar is the **conversion from the previous step**. Step one is always 100%, and the gap between steps is your drop-off. - The Purchase step also surfaces **Revenue** and **AOV** for that step. Reading it top to bottom tells you exactly where people fall out of the [journey](../journeys/) - the steps with the biggest drop are where to focus. ## Split testing a funnel (A/B) **Video guide** A split test lets you run two (or more) versions of a step - say two landing pages - and compare them inside the same funnel. Turn on **Split test (A/B)** on the step you want to test. To set it up: - **Name the split** (for example `split`) and list your **variants** - A and B by default, with an optional label. Add more with **+ Add variant**. - TrueMetriks generates a small **snippet for each variant**. Paste each variant's snippet into the matching version of the page, alongside your normal TrueMetriks tracker. The snippet tags the visitor with which variant they saw, so the funnel can tell them apart. ### Seeing the split-test results When a step is split-tested, the funnel breaks that step out per variant, so you can compare them directly. Each split-tested step shows an **A/B test** badge and a row per variant, with that variant's visits, unique visitors, and conversion - and on the purchase step, its revenue and AOV too. Compare the variants to pick a winner: a variant can win on conversion, on revenue, or both. In the example above, variant B converts better and earns more, so B is the version to keep. # Journeys Source: https://docs.truemetriks.com/dashboard/journeys/ The Journeys page shows the real paths visitors take through your site, page by page, as a left-to-right flow that reveals the routes ending in a sale. ## What you are looking at Every visitor takes a path: they land somewhere, click through a few pages, and either convert or leave. Journeys stacks those paths on top of each other and draws the result as a flow that reads left to right. - Each **colored bar is a page** on your site (its URL path). - The columns are **steps in sequence**: Step 1 is the page visitors were on, Step 2 is where they went next, and so on. - Each **ribbon is a group of visitors** moving from one page to the next. The thicker the ribbon, the more visitors took that hop. Hover any bar or ribbon to see the exact visit count and what share of all journeys it represents. So instead of guessing how people reach your checkout, you watch the actual traffic flow into it. ## Reading a journey Pull any single thread out of the board and it reads like a sentence: this page, then this page, then this one. Here are three routes from the same site: The first route is the money path - home to a course page to checkout to the thank-you page. The second is people who price-shop first and then book a call. The third skips the homepage entirely because the visitor landed straight on a lead page from an ad. Seeing them side by side tells you which entry points actually carry visitors toward a sale. ## Controls - **Steps** - the slider sets how many hops to show, from 2 up to 6. Two steps answers "where do people go from each page"; more steps follows longer routes deeper into the site. - **Journeys** - the second slider caps how many distinct paths are drawn, from 10 up to 200. Keep it low for the clearest, highest-volume routes; raise it to see the long tail. - **Date range** - the period picker (top right) scopes the board to a window, for example the last 60 days. - **Filter** - the standard filters (country, device, browser, referrer, and so on) narrow the board to a segment, so you can compare how, say, mobile visitors flow differently from desktop. ## Filtering to a specific path Each step has its own **Path filter** box. Type a URL path into one and the board only shows journeys that passed through that page at that step, which is how you answer a specific question instead of reading the whole map. Two wildcards make the filters flexible: - `*` matches a **single** path segment. `/course/*` matches `/course/bp-w-v1` and `/course/semsccsbe`, but not `/course/bp-w-v1/lesson-2`. - `**` matches **multiple** segments. `/checkout/**` matches `/checkout/book`, `/checkout/gmiplzk`, and anything deeper under `/checkout/`. For example, put `/checkout/**` in the last step and Journeys shows only the routes that ended at a checkout page - the exact paths that lead to a sale, and how many visitors each one carried. ## What it is good for - Find the dominant route into checkout, and double down on the pages that feed it. - Spot detours - pages where visitors loop back or wander off instead of moving forward. - See which landing pages actually carry traffic toward a booking versus which just bounce. - Compare segments (set a Filter) to see if paid, mobile, or international visitors travel a different path. To follow one specific person's activity rather than the aggregate flow, open their profile from [Users](/docs/dashboard/users/) instead. # Products Source: https://docs.truemetriks.com/dashboard/products/ The Products page ranks every product by revenue, units, views, and conversion, then expands each one into the sources, campaigns, and ads that drove its sales. **Video guide** ## What the Products page is Products turns your Purchase and product-view [events](../events/) into a single ranked table: one row per product, sorted by units sold, with the totals for the whole period pinned across the top. It answers the questions you actually care about - which products bring in the money, which get looked at but not bought, and which convert best - without exporting anything to a spreadsheet. A product appears here as soon as it shows up in your event data. Identity uses the item's `id`, then `sku`, then its name, taken from the products carried on the event, so identify and name your products consistently and they stay merged across views and purchases. ## Attribution and Audience Two dropdowns above the totals rescope the entire page at once - the totals, the table, and every source drill-down under it. - **Attribution** decides which touch gets the credit for a sale. **First-touch** credits the source that first brought the buyer in, **Last-touch** credits the source of the visit where they bought, and **Any-touch** credits every source the buyer passed through. Switching this changes which campaigns and ads a product's sales are assigned to. - **Audience** filters by buyer type: **New + Returning** (everyone), **New** (first-time buyers only), or **Returning** (people who had bought before). Set these once and read the whole page through that lens. ## The four totals The cards across the top are the period totals, scoped to your date range and any active filter. - **Total revenue** - the sum of the `value` on every Purchase event that carries a product, in the selected period. - **Total units sold** - how many quantities of your products were sold in the period, added up across every product. A 3-unit order counts as 3, not 1. - **Total product views** - the number of product-view events (`ViewContent`) for any product. - **Total site visits** - the distinct people who visited your site in the period. This is the denominator for the "Conv. (site to buy)" column. Purchases that arrive with no product information at all are excluded from the table and reported separately below these totals, so the page's total can legitimately sit below your Overview revenue. ## The table columns Each row is one product. Every column is sortable, so click a header to rank by it. - **Views** - how many times this product was viewed (a `ViewContent` event carrying this product). - **Units sold** - how many quantities of this product were sold. A 3-unit order counts as 3, not 1. - **Revenue** - total revenue from this product in the period: this product's share of the order value on each Purchase event that carried it. An order with three products splits its `value` across those three rows, so no product is credited with money that belongs to another. - **Contribution %** - this product's share of total product revenue. The whole column adds up to 100%, so you can see at a glance which few products carry the store. - **Conv. (view to buy)** - of the people who viewed this product, the share who went on to buy it. This is measured per person across all their devices, so someone who views on mobile and buys on desktop still counts as one converted viewer. A dash means the product had no views to convert. - **Conv. (site to buy)** - of everyone who visited your site in the period, the share who bought this product. The numbers are small because the denominator is your whole audience, not just the people who saw the product. It tells you how much of your total traffic a single product manages to convert. The **Total** row at the bottom sums the period: total revenue, total units, 100% contribution, and the blended conversion rates across all products. ## Drill into the sources behind a product Every product row has an arrow on its left. Click it and the row opens into a **source drill-down**: a tree that breaks that single product's sales down by where they came from, so you can see not just that a product sells, but which traffic actually drives it. The tree has just two columns - **Sales** (how many people bought through that node) and **Revenue** (how much it brought in) - and you expand it one level at a time: - **Source** - the top level, like `facebook`, `google`, or `(Direct)` for visitors who arrived with no tags and no referrer (typed the URL, a bookmark, or a link that stripped its referrer). - **Medium** - `paid` versus organic traffic under that source. - **Campaign** - your `CAMPAIGN`-tagged campaigns under that medium. - **Ad set** - the `AD SET` (Facebook) or `AD GROUP` (Google and TikTok) inside a campaign. - **Ad** - the individual `AD` that closed the sale. Open only the branches you care about. The numbers always roll up, so a campaign's sales equal the sum of its ad sets, and a source's sales equal the sum of its campaigns plus any direct or untagged sales. Because the drill-down obeys the Attribution and Audience dropdowns, switching to **Last-touch** or to **New** buyers re-credits and re-counts the whole tree. This is what turns Products from a leaderboard into an action list: when one product carries the store, the drill-down tells you the exact campaign, ad set, and ad to scale. ## Reading it The two conversion columns answer different questions, and the gap between them is the point. - A **high view-to-buy** with a **low site-to-buy** means the product closes well once people find it, but not many visitors reach it. The fix is traffic and merchandising - get more eyes on the page. - A **low view-to-buy** means people look and walk away. The fix is the offer or the page itself - price, copy, proof. - **Contribution %** tells you where to spend that effort. Improving the conversion of a product that is 12% of revenue moves the business; perfecting one that is 0.3% does not. To dig into the orders behind these numbers, see [Revenue](/docs/dashboard/revenue/). To follow how visitors reach a product before buying, see [Journeys](/docs/dashboard/journeys/). # Events Source: https://docs.truemetriks.com/dashboard/events/ Define custom events in TrueMetriks and pick exactly what makes each one fire: a URL match, a click, scroll depth, time on page, or another event firing. ## What this page is for TrueMetriks already tracks the built-in events (PageView, Lead, Purchase, and so on). The Events page is for everything else you want to measure: a newsletter signup, a "Get started" click, someone reaching the bottom of a sales page. You define the event once here, and the tracker on your site starts firing it whenever its condition is met. Where each event is sent (Facebook, Google Ads, [GA4](../../ad-integrations/ga4/), [TikTok](../../ad-integrations/tiktok/)) is handled separately, inside each ad integration card under Settings - Tracking. This page is only about defining the event itself. ## Creating an event Click **Create event** in the top right. The dialog asks for a few details: - **Event name** - the technical name for the event, like `MyCustomEvent`. This is the name your integrations use, so keep it short and consistent. - **Display name (optional)** - a friendly label shown in the dashboard, like "Newsletter Signup". Purely cosmetic. - **Description (optional)** - a note to yourself about what this event represents. - **Active** - the on/off switch. Leave it on and the tracker fires the event; switch it off to pause it without deleting it. ## Choosing how the event fires Every event has exactly one **firing condition**. The **Condition type** dropdown decides what triggers it, and the fields below it change to match your choice. - **URL match** - fires when the visitor lands on a matching page. You pick a **URL operator** (`contains`, `equals`, `starts with`, or `regex`) and a **URL value** like `/thank-you`. For example, "contains `/thank-you`" fires on any page whose address includes `/thank-you`, which is the usual way to catch a confirmation page. - **Button click** - fires when a visitor clicks an element. You target it either by **CSS selector** (like `#cta-button`) or by **Visible text** (like `Get Started`). - **Scroll depth** - fires once the visitor scrolls a set percentage down the page (for example 50%). Good for measuring real engagement on long pages. - **Time on page** - fires after the visitor has stayed a set number of seconds (for example 30). Another simple engagement signal. - **Fires when another event fires** - fires whenever a built-in event fires. This is how you give an existing action a second, custom name. ### Fires when another event fires When you pick this option, you choose a **Source event** - one of the built-in events. Your custom event then fires every time that source event does, and it carries all of the source event's parameters along with it. This is handy when an advertiser needs a differently named event for a platform's rules. For example, point a custom event at the built-in **Purchase** so the same sale also fires under your own name, without changing anything in your checkout. ## After you save Hit **Save** and the event joins your Custom events list, ready to fire. To decide where it gets sent, open the relevant ad integration card under Settings - Tracking and map it there. The built-in events that can power conversions are explained in the [Facebook](/docs/ad-integrations/facebook/) and [Google Ads](/docs/ad-integrations/google-ads/) guides. # Platform Health Source: https://docs.truemetriks.com/dashboard/platform-health/ Platform Health shows whether your server-side conversions are reaching each ad platform, plus a Paid Traffic and Engagement card with paid pageviews, visitors, and per-event trends vs the prior period. **Video guide** The Platform Health page has two cards. The first confirms the conversions TrueMetriks fires server-side are reaching each ad platform. The second shows how much paid traffic and engagement those ads are driving, measured against the period before. ## Conversion delivery Pick a window at the top right (it defaults to the last 24 hours), then read the result: - **Platform tabs** - [Facebook Ads](../../ad-integrations/facebook/), [Google Analytics 4](../../ad-integrations/ga4/), [Google Ads](../../ad-integrations/google-ads/), and any other connected platform, each with its overall success rate. Click one to see just that platform. - **Total fires / Successful / Failed** - the headline counts for the selected platform and window. - **Per event** - the same breakdown split by [event](../events/) (PageView, Lead, Purchase, and so on), with a success rate for each. If anything failed, the row shows the full error detail so you can see exactly why it was rejected. In a healthy account every number sits at 100%, so this part of the page is mostly a quick glance to confirm your tracking is still flowing. ## Paid traffic and engagement The second card, Paid Traffic & Engagement, counts only the visitors and events that came from your ad platforms - Facebook, Google, and TikTok ads. Organic, direct, and referral traffic is excluded, so every number here is what your paid advertising actually produced. A visitor counts as paid based on their first touch, the source that first brought them to your site. This card has its own date range and a **Day / Week / Month** toggle, separate from the conversion-delivery card above. Some ranges lock the toggle to the granularity that fits them - the last 12 months shows by month, shorter ranges by day. ### The headline numbers Three figures sit at the top, each compared with the prior period - the equal-length window immediately before the one you picked: - **Paid pageviews** - total pageviews from paid visitors. - **Unique paid visitors** - how many distinct people those pageviews came from. - **Pageviews per visitor** - paid pageviews divided by unique paid visitors, so you can see how much of your site the average paid visitor explored. Under each number a coloured change shows the move against the prior period: a green up arrow when it grew, a red down arrow when it fell. The grey text beside it ("vs 1,180 prior period") is the prior period's value, so you always see what you are being compared to. ### Paid pageviews over time The chart plots paid pageviews per day, week, or month, following the toggle. Hover any point to compare it with the previous one - the tooltip shows that period's paid pageviews and the percentage change from the period before it, so you can spot where paid traffic ramped up or dropped off. ### Per event vs prior period The table breaks paid activity down by [event](../events/). For each event you get the count and the number of unique people for **this period** and for the **prior period** side by side, plus a **Change** column comparing the two counts. It is the fastest way to see which paid actions - PageView, Lead, Purchase, Book a Call - are climbing or slipping versus the period before. If there was no paid traffic in the window, the table reads "No paid traffic in this period." # Sessions Source: https://docs.truemetriks.com/dashboard/sessions/ Sessions is a live, newest-first feed of every visit to your site, where each card opens into the full ordered event timeline of what that person did. A session is one contiguous visit from a single person. A new session starts after 30 minutes of inactivity, so the same person coming back later shows up as a separate, later visit. The feed is newest first. ## Reading a session card Each card is one visit. Left to right it tells you who came, how, and what they did. - **Visit number and acquisition source.** `VISIT 25` is this person's 25th visit to your site. The `Acquisition source` line shows the source and medium that first brought them in (for example `ig ยท social`). Direct visits have no acquisition line. - **Who.** An avatar plus a name. If you have identified the user (by sending a user ID from your site), you see their real identifier and an Identified badge; otherwise you get a stable anonymous name like "Orange Trout" that stays the same across all of their visits. - **Browser, OS, device.** The three small icons. Traffic from an in-app browser (Instagram, for example) shows that app's icon, which is why social visits look the way they do. - **Pageviews and events.** The eye pill counts pageviews. Any named event that fired in the visit gets its own pill next to it (for example `InitiateCheckout 1`), so you can spot the sessions that actually did something. - **Channel.** The marketing channel TrueMetriks resolved for the visit (Organic Social, Direct, Paid Search, and so on). - **Entry and exit page.** The first page they landed on, an arrow, and the last page they saw. A single path means they only viewed one page. - **When and how long.** The start time and the session duration (first event to last event). ## Narrowing the feed The bar above the list filters which sessions you see: - **Identified only** hides anonymous visitors so you only see people you have a user ID for. - **Min pageviews**, **Min events**, and **Min duration (s)** drop the shallow visits. Set Min duration to a few seconds to clear out the 0-second bounces, or Min events to 1 to see only sessions where something fired. - The **Filter** button and date range at the top scope the feed by any property (country, channel, UTM, page, event name, and more), exactly like the rest of the dashboard. Use the page arrows on the right to move through the feed; it loads 100 sessions per page. ## Opening a session Click any card to expand it in place. You get two tabs and a shortcut to the person's full history. ### Timeline The Timeline is the play-by-play: every event in the visit, in order, numbered down the page. Each pageview shows the URL (the query string, including UTMs and click IDs, is greyed after it) and the time spent on that page before the next pageview. Custom events, outbound clicks, button clicks, copies, form submits, input changes, and errors all appear inline where they happened. The pills at the top filter the timeline by event type. Each one carries a count, so `Pageview 3` means three pageviews in this visit; click a type to show or hide it. Long sessions load in batches with a Load More button, and the footer keeps a running `Showing X of Y events`. The per-page duration is measured to the next pageview, not the next event, so a custom event firing a split second after the page loads never makes a page look like it lasted zero seconds. ### Session Info The Session Info tab is the reference card for the visit, in three columns: - **User Information** - user ID, language, and resolved country, region, and city. - **Device Information** - device type, browser and version, OS and version, screen size, and IP. - **Source Information** - channel, referrer, and entry page. ### View User **View User** jumps from this single visit to that person's complete cross-session profile: every visit they have ever made, stitched together. It is the bridge from "what happened in this session" to "who is this person and what is their whole journey." See [Users](../users/) for that view. # Users Source: https://docs.truemetriks.com/dashboard/users/ Users is the people-level view in TrueMetriks: one row per person with lifetime sessions, events, purchases, and spend, plus a full per-person profile. Every person who has been on your site shows up here once, whether or not you have identified them. Anonymous visitors get a stable name and ID that persists across all of their visits; once your site identifies them with a user ID, their real identifier takes over. The numbers on each row are lifetime totals, not just today. ## Reading the table Each row is one person; the columns are their lifetime profile at a glance. - **User** - the avatar and name. Anonymous people keep a stable generated name (like "Black Puma"); identified people show the ID you sent. - **Country, Channel, Browser, OS, Device** - where they are and how they browse, taken from their most recent visit. Traffic from an in-app browser (Instagram, for example) shows that app. - **Pageviews, Events, Sessions** - lifetime counts across every visit. - **Purchases** and **Total Spent** - how many purchase events they have fired and the summed value, so your buyers rise to the top. - **Last Seen** and **First Seen** - recency and how long they have been a known visitor. Every metric column is sortable: click a header to rank by it. Sort by Total Spent to find your highest-value customers, or by Last Seen to see who came back today. ## Tabs, search, and filters - **Users / Traits / Imported customers** - the Users tab is this list. Traits shows the custom attributes you have attached to people; Imported customers shows records you have brought in from outside the tracker. - **Search by username** narrows the list to a single person by name or ID. - **Identified only** hides anonymous visitors so you see just the people you have a user ID for. - **Buyers only** keeps only people with at least one purchase. ## Opening a profile Click any row to open that person's profile. It is everything TrueMetriks knows about them in one place: their lifetime stats, where and how they browse, when they were active, the pages they return to, and every visit they have made. The profile is built from these blocks: - **Lifetime stats** - total sessions, pageviews, and events, their average session duration, and the first and last time you saw them. - **Location and Device** - country, region, and language, plus the device, browser, OS (with versions), and screen size from their latest visit. A dash means that detail was not available. - **Activity Calendar** - a day-by-day heatmap of when this person was active, so you can see whether they are a one-time visitor or a regular. - **Top Pages** - the pages they have spent the most sessions on, ranked. - **Visit history** - every session this person has had, newest first, in the same card format as the [Sessions](../sessions/) feed. ## Visit history and the timeline Each visit in the history is the same card you see on the Sessions page, and it expands the same way. Click one to open its full event timeline. The Timeline lists every event in that visit in order, with the time spent on each page, and the type pills up top filter it. Open Session Info on any visit for the complete device, location, and source breakdown for that session. For the full mechanics, see [Sessions](../sessions/). **Video guide** ## How a person becomes identified Until you tell TrueMetriks who someone is, they are tracked as a stable anonymous person so none of their history is lost. When your site identifies a visitor with a user ID (an email, a customer ID, whatever you use), that visitor's past and future visits collapse onto the one identified profile. # UTM Builder Source: https://docs.truemetriks.com/dashboard/utm-builder/ The UTM Builder creates correctly-encoded, tagged campaign URLs and copy-ready Facebook, Google Ads, and TikTok templates that wire up full TrueMetriks attribution. UTMs are the `utm_source`, `utm_medium`, and `utm_campaign` tags you add to a link so analytics tools know where a visitor came from. TrueMetriks reads them on every click and uses them for channel attribution across the [dashboard](../main/). This page builds those tagged URLs for you and gives you ready-made templates for the ad platforms. ## The builder Fill the form top to bottom. The output updates live as you type. **Video guide** - **URL** is the destination the ad or link points to. The UTMs are appended to it; if the URL already has UTMs, they are replaced. - **Your conventions** lets you save a set of values you reuse often and load them back with one click, so your tags stay consistent every time. See [Save your naming conventions](#save-your-naming-conventions) below. - **Source** (required) is the platform the traffic comes from, like `facebook`, `google`, or `tiktok`. - **Medium** (required) is the marketing medium: `cpc` for paid ads, `email` for newsletters, `social` for organic posts. - **Campaign** (required) is the campaign name. Keep it lowercase with underscores or hyphens for consistency. - **Term** (optional) is the paid-search keyword, when relevant. Usually blank for non-search ads. - **Content** (optional) distinguishes similar ads pointing at the same URL, which is what you use for A/B testing creative variants. The fields marked with a red asterisk are required; the rest are optional. ## Save your naming conventions The single most important habit with UTMs is consistency: pick one naming convention and use it every time. If one campaign is tagged `youtube` / `social` and the next is `YouTube` / `Social`, TrueMetriks reads them as two different sources and your reports split in two. Saved conventions are how you lock that consistency in so you never have to remember the exact spelling again. Fill the fields the way you want them once, then click **Save current** and give the convention a name. Only the name is required; the source, medium, campaign, term, and content come pre-filled from what you typed, and you can adjust any of them before saving. Once saved, the convention shows up as a chip under **Your conventions**. Click the chip and it pre-fills the source, medium, campaign, term, and content fields for you, so you never retype them or risk a typo. It leaves the URL field alone, since the destination changes from link to link. Saved conventions live on the site, not in your browser, so everyone on your team with access to that site sees the same list and tags links the same way. ### Manage, edit, and delete Click **Manage** to see every convention you have saved. Drag them to reorder; the top five show as chips. The pencil opens the same dialog as Save current (titled **Edit convention**) so you can rename a convention or change its values, and the trash removes it. Deleting asks you to confirm first, so you will not remove a convention by accident. ### Why naming conventions matter Attribution is only as good as the tags feeding it. One convention, used everywhere, keeps every click rolling up to the right source, medium, and campaign across your whole [dashboard](../main/). Decide on your names once, then never switch the spelling, casing, or wording mid-stream. For the full picture of why this matters and how to structure your names, read [Organize your data for better tracking]({{marketing}}/blog/organize-your-data-for-better-tracking/) on our blog. ## The output Once the form is filled, the builder gives you two things to copy. Here is what they look like for the example above; both stay empty until the form is valid. - **Full URL** is the complete, ready-to-share link with the UTMs already on it. Paste it anywhere you share a link directly: an email, a social post, a link-in-bio. - **Ad platform UTM string** is just the parameters, with no URL in front. Paste this into an ad platform's "URL parameters" field; the platform appends it to your destination URL automatically when it serves the ad. Its label changes to match the preset you picked (for example "Facebook URL parameters string"). ## Short links A full UTM URL is long and not very pretty: it carries every parameter the form just built. That is fine for an ad platform, but not for somewhere people actually see the link, like a YouTube description, an Instagram or LinkedIn bio, a printed QR code, or a podcast show-note. The Short link card, right below the Full URL, fixes that. It turns the Full URL into a short, branded link on your own tracking subdomain, so `haven.your-site.com/go/summer-sale` redirects to the long tagged URL and every click still carries the full UTM tagging. Because the short link lives on your own verified subdomain, the same one you set up in [Connect your domain](../../getting-started/cname-setup/), it looks like you and not a third-party shortener, and it keeps working for as long as your domain does. The card only appears once your domain is connected and verified; until then it shows a prompt to set that up first. **Video guide** Click **Create short link** to open the dialog. - **Name** (required) is a label for you, so you can find the link again later, like `Summer sale - Facebook` or `June newsletter`. It is never shown to visitors. - **Short link** is the part after `/go/`. Leave it blank and TrueMetriks generates a short random slug for you, or type your own to get a clean, memorable one like `summer-sale`. You can use letters, numbers, dashes, and underscores. - **Destination URL** is where the link sends people. It comes pre-filled with the Full URL the builder just made, so the redirect carries all your UTM tags; paste a different URL here if you want. Click **Save** and the link appears in the card below. Each row shows its name and the full short URL, with three actions: **copy** the short link to your clipboard, **edit** its name, slug, or destination, and **delete** it (deleting asks you to confirm first). Use the **Search by name** box to find a link once you have built up a lot of them. Short links are saved per-site, so everyone on your team with access to that site shares the same list. ## Per-platform templates **Video guide** This is the part you actually paste into your ad platforms, and it is different from the single link above. For ads you do not build a URL per click; you hand the platform one template string and it tags every click for you. The templates use each platform's own macros, so the platform fills in the real campaign, ad, and creative IDs at serve time, and they carry the extra TrueMetriks attribution parameters the rest of the platform depends on. Copy each one with its Copy button. The only thing you have to get right is where the string goes, and that is different for each platform. **[Facebook](../../ad-integrations/facebook/): paste it on each ad (ad level).** Facebook has no account-wide place to put URL parameters, so the template lives at the ad level. Select the ad, scroll down to its **Tracking** section, and paste the Facebook string into the **URL parameters** field. You repeat this for every ad you create. That is what makes Facebook's `{{campaign.id}}` / `{{ad.id}}` macros resolve per ad. **[Google Ads](../../ad-integrations/google-ads/): paste it once at the account level.** Google does have an account-wide slot, so you only set it a single time. Go to **Admin**, then **Account settings**, open the **Tracking** section, and paste the Google string into the **Tracking template** field, then click Save. Every campaign and ad in the account inherits it automatically. There is nothing to repeat per ad and no scripts to install. (`{lpurl}` at the front is Google's macro for "the landing page URL," so the template wraps whatever destination each ad already points to.) **[TikTok](../../ad-integrations/tiktok/): paste it on each ad (ad level).** Like Facebook, TikTok has no account-wide slot, so the template lives at the ad level. In TikTok Ads Manager, select the ad and paste the TikTok string into the ad's **URL** / tracking parameters field, then repeat for every ad. Use the macros exactly as written; TikTok resolves them to real numeric IDs on real ad clicks (in previews they can stay literal, which is normal): ```text utm_source=tiktok&utm_medium=paid&utm_campaign=__CAMPAIGN_ID__&utm_content=__CID__&utm_term=__AID__&ttclid=__CLICKID__&r_ad_id=__CID__&r_adgroup_id=__AID__&r_campaign_id=__CAMPAIGN_ID__&placement=__PLACEMENT__ ``` The TikTok macros map onto the standard UTMs the same way the other platforms' do: `__CID__` is the ad (creative) ID and fills `utm_content`, `__AID__` is the ad group ID and fills `utm_term`, and `__CAMPAIGN_ID__` is the campaign ID and fills `utm_campaign`. `__CLICKID__` becomes `ttclid`, TikTok's click ID, and the `r_*` parameters carry the same raw IDs through to TrueMetriks. One TikTok-specific requirement: when you create your TikTok pixel in TikTok Events Manager, choose **Pixel + Events API** or **Events API** as the connection method, not Pixel only. The [TikTok integration](../../ad-integrations/tiktok/) page walks through the full setup. The macro tokens like `{{campaign.id}}` (Facebook), `{creative}` (Google), and `__CID__` (TikTok) are placeholders the ad platform swaps for real IDs as each ad runs. The extra `r_*`, `gc_id`, and `placement` parameters carry those raw platform IDs through to TrueMetriks so attribution stays correct even when ad IDs collide across accounts. # API Source: https://docs.truemetriks.com/dashboard/api/ The TrueMetriks v1 API in one place: create an API key on the Integrations tab, authenticate with a Bearer token, and call the read-only analytics endpoints. The API is read-only: you use it to fetch your own analytics data, never to send data in. ## Let an AI set this up for you Copy the prompt below into Claude, ChatGPT or any other AI. It carries everything the AI needs: where to get your key, the base URL, the auth header, the parameters and every endpoint. It asks you for your key first, then writes and runs the requests. ```{ .text .docs-prompt } I use TrueMetriks, a server-side tracking and conversion attribution platform. Help me pull my own analytics out of it through its read-only REST API, and then answer questions about that data. BEFORE YOU WRITE ANY CODE, ask me for these two things and wait for my reply: 1. My TrueMetriks API key. 2. My site_id (a whole number). Tell me exactly where to find them: - Sign in at https://app.truemetriks.com - Open "Settings" for the site you want (the site's OWN settings, NOT Account Settings), then click the "Integrations" tab. - In the tile grid, after "Webhooks", click the "API Keys" tile. - Under "Create API key", type a name (for example "ai-assistant"), click "Create", and copy the key from the dialog that appears. - IMPORTANT: the key is shown ONCE and cannot be looked up again. Tell me to paste it somewhere safe, such as a password manager, before closing the dialog. If it is lost, the only fix is to create a new key. - API access requires the Unlimited plan. If the API Keys tile is greyed out with a padlock reading UNLIMITED, I am on a lower plan and cannot use the API until I upgrade. - My site_id is the number in the dashboard URL while that site is open, for example https://app.truemetriks.com/42/main means site_id is 42. It is also in the settings URL: /settings/websites/42/integrations. Never print my key back to me in full, never paste it into a public place, and never commit it to a repository. Read it from an environment variable such as TM_API_KEY instead of hard coding it. HOW THE API WORKS Base URL: https://app.truemetriks.com/v1 Auth: send this header on every request Authorization: Bearer Method: every endpoint is GET and read-only. Responses are JSON. Shared query parameters, accepted by every endpoint: site_id REQUIRED. Whole number. My site. period Optional shorthand. One of: today, yesterday, last_7d, last_30d, last_90d, last_365d. If given, it overrides from/to. from, to Optional explicit dates, YYYY-MM-DD, both or neither. Inclusive. timezone Optional IANA timezone, for example America/New_York. Default UTC. If neither period nor from/to is given, the range defaults to the last 30 days. The range may not exceed 366 days. There are NO start_date or end_date parameters: sending them does nothing and you silently get the last 30 days. There is no previous-period shortcut, so to compare two periods call the endpoint twice with explicit from/to ranges. All money fields come back as decimal dollars (for example 4590.50), never cents, rounded to 2 decimal places. THE SEVEN ENDPOINTS GET /v1/overview Top-level KPIs plus a per-source breakdown. The only endpoint that returns per-source SESSION counts, so use it for revenue-per-session questions. Extra params: none. Returns: period{from,to,timezone}, totals{sessions,unique_visitors,pageviews,revenue,leads,purchases}, by_source[{source,channel,sessions,revenue,leads,purchases}] GET /v1/revenue Any "how much money" question. Extra params: none. Returns: period, totals{revenue,purchases,average_order_value}, by_source[{source,channel,revenue,purchases,conversion_rate_pct}], by_product[{sku,product_name,revenue,units}], by_day[{date,revenue,purchases}] Note: conversion_rate_pct is sessions-to-purchase for that source, NOT leads-to-purchase. For leads-to-purchase, divide /v1/revenue totals.purchases by /v1/leads totals.leads. GET /v1/leads Lead counts with source attribution. This returns COUNTS, not a list of people, and no personal data. Extra params: none. Returns: period, totals{leads}, by_source[{source,channel,leads}] GET /v1/pages Top pages by pageviews. No source breakdown, by design. Extra params: limit (1 to 100, default 25). Returns: period, pages[{path,pageviews,unique_visitors,avg_time_on_page_seconds, bounce_rate_pct}] GET /v1/products Per-product revenue and units. Extra params: none. Returns: period, products[{sku,product_name,units_sold,revenue,average_price}], by_source[{source,channel,top_product_sku,top_product_name,revenue}] GET /v1/users Visitor counts and segmentation. Aggregate counts only, not a list of people. Extra params: none. Returns: period, totals{unique_visitors,returning_visitors,new_visitors}, by_source[{source,channel,visitors}], by_country[{country,visitors}] GET /v1/funnels The site's saved funnels with per-step counts and conversion rates. The funnels array is empty if none are defined. Extra params: none. Returns: period, funnels[{id,name,overall_conversion_pct, steps[{step_name,visitors,conversion_from_previous_pct}]}] ERRORS TO EXPECT 401 AUTH_REQUIRED no Authorization header 401 INVALID_API_KEY key not found, expired or revoked 403 TIER_NO_API_ACCESS my plan does not include API access 403 SITE_ACCESS_DENIED key is valid but has no access to that site_id 400 MISSING_SITE_ID site_id was not supplied 400 BAD_DATE_FORMAT dates are not YYYY-MM-DD, or only one of from/to 400 RANGE_TOO_WIDE range is longer than 366 days 429 RATE_LIMITED 200 requests per minute per key was exceeded 504 QUERY_TIMEOUT query took over 10 seconds, narrow the range WHAT TO DO 1. Ask me for the key and site_id, as described above. 2. Write a script (bash with curl, or Python with requests) that reads the key from the TM_API_KEY environment variable. 3. Start with a single call to /v1/overview for period=last_7d to confirm the key works, and show me the result. 4. Then ask what I actually want to know, and use the endpoints above to answer it. If a question needs two date ranges, call the endpoint twice. If you are in a chat-only tool with no way to run code or make HTTP requests, say so, then give me a ready-to-run script I can run myself and paste the output back to you. ``` ## You need the Unlimited plan API access is on the **Unlimited** plan only. On Starter and Growth the API Keys tile is greyed out with a padlock chip reading `UNLIMITED`, so no key can be created. If a workspace drops to a lower plan, its keys stay in place but every request is refused: ```json { "error": "API access is available on the Unlimited plan. Please upgrade to use the API.", "code": "TIER_NO_API_ACCESS" } ``` Upgrading restores access immediately, with the same key. ## Creating an API key API keys live on the site's own **Integrations** tab. They are no longer in Account Settings. 1. Open **Settings** for the site you want (the site's settings, not Account Settings). 2. Click the **Integrations** tab. 3. After **Webhooks**, click the **API Keys** tile. Its status line reads `No keys yet`, `1 key` or `N keys`. 4. Type a name under **Create API key** and click **Create**. Name it after where it will be used, like `reporting-script`, so you can revoke just that one later. The full key is shown **once**, in a dialog. Copy it then and store it somewhere safe. Afterwards the list keeps only a masked preview, and a lost key cannot be recovered: you have to create a new one. Click **Delete** next to a key to revoke it. Revocation is immediate and cannot be undone. ### What the key list does not show This list shows only the keys created on this page. Deliberately hidden: keys from the [WordPress plugin](../../platform-installs/wordpress/) (named `wp-plugin...`), the MCP key, and revoked keys. **Never delete a `wp-plugin` key.** It would break that site's plugin. It is hidden here precisely so that cannot happen by accident. Keys are scoped to one workspace and inherit your site access, so a key can only read sites you can already see. ## Base URL ``` https://app.truemetriks.com/v1 ``` There is no separate API hostname. Every endpoint sits under the same origin as the dashboard. ## Authentication Every request requires an `Authorization` header with your key as a Bearer token: ``` Authorization: Bearer YOUR_API_KEY ``` A missing or malformed header returns `401 AUTH_REQUIRED`. A key that is unknown, expired or revoked returns `401 INVALID_API_KEY`. A valid key asking for a site you cannot access returns `403 SITE_ACCESS_DENIED`. Keep the key out of your source code. Read it from an environment variable instead: ```bash export TM_API_KEY="your_key_here" curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/overview?site_id=42&period=last_7d" ``` ## Common query parameters Every endpoint accepts these: | Param | Required | Description | |-----------|----------|----------------------------------------------------------------------------------------| | site_id | yes | The site you are querying, as a whole number | | period | no | `today`, `yesterday`, `last_7d`, `last_30d`, `last_90d`, `last_365d`. Overrides from/to | | from | no | Start date, `YYYY-MM-DD`, inclusive. Must be paired with `to` | | to | no | End date, `YYYY-MM-DD`, inclusive. Must be paired with `from` | | timezone | no | IANA timezone for date bucketing, for example `America/New_York`. Default `UTC` | Before your first request: - **There is no `start_date` or `end_date`.** Sending them has no effect, and since the range is optional you get the default last 30 days back with no error to warn you. - Send neither `period` nor `from`/`to` and the range defaults to **the last 30 days**. - `from` and `to` come as a pair. Sending one returns `400 BAD_DATE_FORMAT`. - The range may not exceed **366 days** (`400 RANGE_TOO_WIDE`), and `from` must not be later than `to` (`400 INVERTED_RANGE`). - There is no previous-period shortcut. To compare two periods, call the endpoint twice. Every response echoes the range it used, so you can confirm what you got: ```json "period": { "from": "2026-07-06", "to": "2026-08-05", "timezone": "UTC" } ``` ### Money and rounding All monetary fields are returned as decimal dollars, for example `4590.50`, never cents. Values are rounded to 2 decimal places on the server. ### Caching Responses are cached for 60 seconds per unique set of parameters. The `X-Cache` response header reads `HIT` or `MISS`. To force a fresh read, send `Cache-Control: no-cache`. ## Endpoints All seven endpoints are `GET` and read-only. ### GET /v1/overview Top-level KPIs for the period plus a per-source breakdown. This is the only endpoint that returns per-source **session** counts, so use it whenever you need revenue per session or sessions per lead. Parameters: the common parameters only. ```bash curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/overview?site_id=42&period=last_7d" ``` ```json { "period": { "from": "2026-05-08", "to": "2026-05-15", "timezone": "UTC" }, "totals": { "sessions": 4321, "unique_visitors": 3210, "pageviews": 12345, "revenue": 4590.50, "leads": 287, "purchases": 32 }, "by_source": [ { "source": "facebook", "channel": "Paid Social", "sessions": 2100, "revenue": 2750.00, "leads": 140, "purchases": 18 }, { "source": "google", "channel": "Paid Search", "sessions": 1100, "revenue": 900.50, "leads": 70, "purchases": 9 } ] } ``` `channel` is the marketing ecosystem the session was classified into at ingest, the same grouping the dashboard [Main page](../main/) uses. ### GET /v1/revenue Revenue totals with breakdowns by source, by product and by day. Use it for any "how much money" question. Parameters: the common parameters only. ```bash curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/revenue?site_id=42&period=last_30d" ``` ```json { "period": { "from": "2026-04-15", "to": "2026-05-15", "timezone": "UTC" }, "totals": { "revenue": 23790.00, "purchases": 255, "average_order_value": 93.29 }, "by_source": [ { "source": "facebook", "channel": "Paid Social", "revenue": 12450.00, "purchases": 134, "conversion_rate_pct": 4.2 } ], "by_product": [ { "sku": "TM-PRO-001", "product_name": "Premium Subscription", "revenue": 11900.00, "units": 100 } ], "by_day": [ { "date": "2026-05-14", "revenue": 412.50, "purchases": 5 } ] } ``` `conversion_rate_pct` is **sessions to purchase** for that source: purchases from the source divided by sessions from the source, times 100. It is not leads to purchase. For that, divide this endpoint's `totals.purchases` by `/v1/leads` `totals.leads`. ### GET /v1/leads Lead counts with source attribution. This returns counts, not a list of people, so no personal data comes back from it. Parameters: the common parameters only. ```bash curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/leads?site_id=42&period=last_30d" ``` ```json { "period": { "from": "2026-04-15", "to": "2026-05-15", "timezone": "UTC" }, "totals": { "leads": 287 }, "by_source": [ { "source": "facebook", "channel": "Paid Social", "leads": 140 }, { "source": "google", "channel": "Paid Search", "leads": 70 } ] } ``` ### GET /v1/pages Top pages by pageviews for the period. There is no source breakdown here by design. Parameters (common parameters plus): | Param | Description | |-------|-----------------------------------------| | limit | Rows to return, 1 to 100. Default 25 | ```bash curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/pages?site_id=42&period=last_7d&limit=5" ``` ```json { "period": { "from": "2026-05-08", "to": "2026-05-15", "timezone": "UTC" }, "pages": [ { "path": "/", "pageviews": 5400, "unique_visitors": 3200, "avg_time_on_page_seconds": 32, "bounce_rate_pct": 41.2 } ] } ``` ### GET /v1/products Per-product revenue and units, plus the top product per source. Covers any Purchase event that shipped product details. Parameters: the common parameters only. ```bash curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/products?site_id=42&period=last_30d" ``` ```json { "period": { "from": "2026-04-15", "to": "2026-05-15", "timezone": "UTC" }, "products": [ { "sku": "TM-PRO-001", "product_name": "Premium Subscription", "units_sold": 100, "revenue": 11900.00, "average_price": 119.00 } ], "by_source": [ { "source": "facebook", "channel": "Paid Social", "top_product_sku": "TM-PRO-001", "top_product_name": "Premium Subscription", "revenue": 8700.00 } ] } ``` ### GET /v1/users Visitor counts and segmentation: new against returning, by source, and by country. These are aggregate counts, not a list of people. Parameters: the common parameters only. ```bash curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/users?site_id=42&period=last_30d" ``` ```json { "period": { "from": "2026-04-15", "to": "2026-05-15", "timezone": "UTC" }, "totals": { "unique_visitors": 8200, "returning_visitors": 1100, "new_visitors": 7100 }, "by_source": [ { "source": "facebook", "channel": "Paid Social", "visitors": 4100 } ], "by_country": [ { "country": "US", "visitors": 5200 }, { "country": "DE", "visitors": 410 } ] } ``` ### GET /v1/funnels Your saved [funnels](../funnels/) with per-step visitor counts and conversion rates. If the site has no funnels defined, `funnels` comes back as an empty array. Parameters: the common parameters only. ```bash curl -H "Authorization: Bearer $TM_API_KEY" \ "https://app.truemetriks.com/v1/funnels?site_id=42&period=last_30d" ``` ```json { "period": { "from": "2026-04-15", "to": "2026-05-15", "timezone": "UTC" }, "funnels": [ { "id": 1, "name": "Checkout Funnel", "steps": [ { "step_name": "Product Page", "visitors": 1000, "conversion_from_previous_pct": 100 }, { "step_name": "Add to Cart", "visitors": 350, "conversion_from_previous_pct": 35.0 }, { "step_name": "Purchase", "visitors": 50, "conversion_from_previous_pct": 14.29 } ], "overall_conversion_pct": 5.0 } ] } ``` `conversion_from_previous_pct` compares each step with the step before it, while `overall_conversion_pct` compares the last step with the first. ## Errors Every error returns a JSON body with an `error` message and a machine-readable `code`. | Status | Code | Meaning | |--------|---------------------|----------------------------------------------------------------------| | 401 | AUTH_REQUIRED | No `Authorization: Bearer` header | | 401 | INVALID_API_KEY | Key not found, expired, or revoked | | 403 | TIER_NO_API_ACCESS | The workspace's plan does not include API access | | 403 | SITE_ACCESS_DENIED | Key valid but the caller has no access to `site_id` | | 400 | MISSING_SITE_ID | `site_id` was not supplied | | 400 | BAD_SITE_ID | `site_id` is not a positive whole number | | 400 | BAD_DATE_FORMAT | Dates are not `YYYY-MM-DD`, or only one of `from`/`to` was sent | | 400 | INVERTED_RANGE | `from` is later than `to` | | 400 | RANGE_TOO_WIDE | The range is longer than 366 days | | 400 | BAD_PERIOD | `period` is not one of the six accepted values | | 400 | BAD_TIMEZONE | `timezone` is not a valid IANA name | | 429 | RATE_LIMITED | Per-key rate limit hit | | 504 | QUERY_TIMEOUT | The query took longer than 10 seconds. Narrow the date range | | 500 | INTERNAL | Something failed on our side | ## Rate limits Keys created on the Unlimited plan allow **200 requests per minute**, measured over a rolling 60 second window. Going over returns `429 RATE_LIMITED`; wait for the window to roll and retry. The 60 second cache in front of every endpoint means a script that polls the same query repeatedly costs you far less than it looks: repeated identical requests inside that window come back from cache. ## Building something bigger The prompt at the top of this page suits an ordinary AI chat. For an **agentic coding tool** that can create files and run commands, like **Claude Code** or **Codex**, copy the whole page instead. Open it in an empty folder, paste, and say what you want (for example, "fetch my revenue for last month and save it to a CSV"). Everything it needs is here.

Then paste it into Claude Code or Codex and tell it what data you want.

## llms.txt For LLM crawlers and code-completion tools, we publish a flat, summary-only index of this docs site at: ``` https://docs.truemetriks.com/llms.txt ``` It lists one line per docs page (title, URL, one-sentence description), grouped by section. It excludes long-form page bodies, the blog, and any anonymized customer data. A companion file, `llms-full.txt`, carries the full text of every page. Both regenerate on every docs deploy, and are byte-identical to the previous deploy when nothing changed. That file describes the documentation. For the **API itself** there is a separate machine-readable manifest, served by the app rather than the docs site, which an AI can fetch directly: ``` https://app.truemetriks.com/api-docs/llms.txt https://app.truemetriks.com/api-docs/openapi.json ``` The OpenAPI file is what you point a ChatGPT Custom GPT at when you want it to call the API as an Action. # MCP (AI access) Source: https://docs.truemetriks.com/dashboard/mcp/ Connect Claude, Cursor or any MCP client to your TrueMetriks analytics and ask questions in plain language. Generate the key on the Integrations tab. Point your own AI at your TrueMetriks data and ask questions in plain language, instead of reading the dashboard yourself. ## Let an AI set this up for you Copy the prompt below into Claude, ChatGPT or any other AI. It carries the whole setup: where to generate the key, both client configs, and where each client keeps its config file. ```{ .text .docs-prompt } I use TrueMetriks, a server-side tracking and conversion attribution platform. It offers MCP access, which lets an AI client read my analytics directly. Walk me through connecting my AI client to it, step by step. BEFORE ANYTHING ELSE, ask me these two things and wait for my reply: 1. Which AI client I want to connect (for example Claude Desktop, Claude Code, Cursor, or something else). 2. My TrueMetriks MCP key. Tell me exactly where to get the key: - Sign in at https://app.truemetriks.com - Open "Settings" for the site I want (the site's OWN settings, NOT Account Settings), then click the "Integrations" tab. - In the tile grid, after "Webhooks", click the "MCP (AI access)" tile. - If my workspace has more than one site, pick the site under "Site this key reads" first. A key reads exactly ONE site. - Click "Generate key" and copy the key from the dialog. - IMPORTANT: the key is shown ONCE and cannot be looked up again. Tell me to paste it somewhere safe, such as a password manager, before closing the dialog. If it is lost, the only fix is to generate a new one, which stops the old one working immediately. - Only workspace OWNERS and ADMINS can do this. If I do not see the MCP tile at all, I am a plain member and need an owner or admin to do it for me. - MCP requires the Unlimited plan. If the tile is greyed out with a padlock reading UNLIMITED, I am on a lower plan and need to upgrade first. Never print my key back to me in full, never paste it into a public place, and never commit it to a repository. THE SERVER URL: https://app.truemetriks.com/mcp Transport: Streamable HTTP (POST) Auth: Authorization: Bearer THE TWO CONFIGS, WHICH ARE NOT INTERCHANGEABLE Claude Desktop is stdio only. It has no field for a bearer header, so it needs the npx mcp-remote bridge. Its config file is claude_desktop_config.json: macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json Give it EXACTLY this, with my key in place of YOUR_MCP_KEY: { "mcpServers": { "truemetriks": { "command": "npx", "args": [ "-y", "mcp-remote", "https://app.truemetriks.com/mcp", "--header", "Authorization: Bearer YOUR_MCP_KEY" ] } } } Cursor, Claude Code and other remote-capable clients take the url and headers directly and must NOT be given the bridge form. Give them EXACTLY this: { "mcpServers": { "truemetriks": { "url": "https://app.truemetriks.com/mcp", "headers": { "Authorization": "Bearer YOUR_MCP_KEY" } } } } Cursor keeps this in ~/.cursor/mcp.json, or .cursor/mcp.json inside a project. Claude Code keeps it in .mcp.json in the project root. Tell me to restart the client after saving the config. THE 16 TOOLS AND HOW TO CALL THEM Once connected you will also receive these from the server, but here they are so you know what is available before we start, and can pick the right one. Every tool takes start_date and end_date as YYYY-MM-DD strings, EXCEPT get_buyer_detail and compare_periods. NO tool takes a site or organization parameter: the key is bound to one site, so never ask me which site I mean and never invent such an argument. Optional arguments are marked. get_overview(start_date, end_date) Sessions, pageviews, unique users, bounce rate, session duration. get_channel_breakdown(start_date, end_date, traffic?, events?, attribution?) By marketing ecosystem (facebook, google, tiktok, email, direct): visits, visitors, revenue, ad spend, ROAS. traffic is "all" (default), "paid" or "organic". events is a comma-separated list of conversion event names to count per row, for example "Lead,Purchase" - names are CASE-SENSITIVE, so call list_event_names first. attribution is "first" (default), "last" or "any". get_campaign_performance(start_date, end_date, events?, attribution?) Per campaign: visits, revenue, spend, ROAS, cost per sale. events and attribution work exactly as above. get_ad_breakdown(start_date, end_date, level, platform?, campaign?, adset?, events?, attribution?, limit?) Per campaign, per ad set or per ad. level is REQUIRED: "campaign", "adset" or "ad". This is the tool that splits a conversion by AD NAME. EVERY call answers at exactly the level asked for, filtered or not: level "ad" with platform "facebook" returns Facebook ADS. Every row carries its own level plus campaign_name / adset_name / ad_name for itself and everything above it, so report an ad as "campaign X > ad set Y > ad Z" rather than as a bare name; a campaign row carries only campaign_name because it aggregates every ad set under it. Narrow with platform ("facebook", "google", "youtube", "tiktok", "instagram"), campaign or adset - each removes rows and changes no number, so a filtered row's metrics equal its unfiltered ones. To describe one platform end to end, call three times for it: level "campaign", then "adset", then "ad", and stitch the rows with campaign_name and adset_name. A campaign filter needs level "adset" or "ad", an adset filter needs level "ad". limit is 1-200, default 50. get_ad_spend(start_date, end_date, group_by?) Spend from synced ad accounts. group_by is "platform" (default) or "campaign". Top 100 rows; check the truncated flag. get_revenue_summary(start_date, end_date) Revenue, purchases, average order value, conversion rate, LTV. This is the ATTRIBUTED view and matches the dashboard exactly, so use it for headline revenue and for any LTV figure I might compare against the dashboard. get_product_performance(start_date, end_date) Per product: revenue, orders, average order value. get_customer_ltv(start_date, end_date) Per-buyer spend distribution: average and median LTV, repeat rate. Counted straight from Purchase events with NO attribution scoping, so it can differ from get_revenue_summary. Do not present the two as the same metric. get_buyer_journeys(start_date, end_date, limit?) Per-buyer lifetime attribution: first touch, last touch, sessions, revenue, days to purchase. limit is 1-100, default 20. get_buyer_detail(buyer_id) Full event timeline for ONE buyer, including the campaign_name, adset_name and ad_name each visit came from plus the raw UTM tagging - use it to say which AD won a named customer. buyer_id is an email or visitor id as returned by get_buyer_journeys. Takes NO dates. Capped at 500 events. get_time_to_purchase(start_date, end_date) First visit to first purchase: buyer count, average and median days. get_event_funnel(start_date, end_date, from_event, to_event?) Event-to-event conversion per person, with average and median hours between. from_event is REQUIRED, for example "Lead" or "Schedule". to_event defaults to "Purchase". get_funnel_ab_results(start_date, end_date, funnel_id?) Saved funnels including A/B variant splits. Call WITHOUT funnel_id first to list the saved funnels, then again with the id you want. get_traffic_dimensions(start_date, end_date, dimension) Top pages or referrers. dimension is REQUIRED: "pathname" or "referrer". Top 25 only; check the truncated flag. list_event_names(start_date, end_date) Every event name the site recorded in the range, with how many times each fired. Call this BEFORE any tool that takes an event name: names are case-sensitive and a near-miss returns 0 rather than an error. compare_periods(tool, period_a_start, period_a_end, period_b_start, period_b_end) Runs another tool over two ranges and returns both. tool is the NAME of one of the tools above, for example "get_revenue_summary". Use this for any this-versus-that question rather than calling a tool twice yourself. Resolve relative dates like "last week" into real YYYY-MM-DD values before calling, and tell me the exact range you used so I can check it. Good first questions to suggest to me: - How much revenue did I make last week, and which channel drove it? - What is my best performing campaign by ROAS this month? - How long do buyers take from first visit to first purchase? - Compare this month with last month. LIMITS TO KNOW - One request may span at most 180 days. Longer ranges are refused, so split them into shorter periods. - 30 requests per minute and 2000 per day per key, with at most 3 running at once. - Everything is read-only. Nothing my AI does can change my data. IF IT DOES NOT WORK 401 the key is wrong, revoked, or is a plain API key rather than an MCP key. 403 TIER_NO_MCP means the workspace is not on the Unlimited plan. 429 too many requests, wait and retry. Tell me to restart the AI client after any config change, because most clients only read their MCP config at startup. ``` ## What MCP is, and what it costs MCP is a standard way for an AI client to call an outside tool. Connect TrueMetriks once and you can ask things like "how much revenue did Facebook bring me last week" or "which campaign has the best ROAS this month" and get the answer straight from your own data. The important part: **you use your own AI client and your own tokens.** Whatever you already pay for Claude, Cursor or your AI of choice covers the conversation. We do not charge per message or per token, which is why there is no usage fee on this feature. Everything MCP exposes is **read-only**. Your AI can read your analytics; it cannot change a setting, edit data, or spend money. ## What you need - **The Unlimited plan.** On Starter and Growth the tile is greyed out with a padlock chip reading `UNLIMITED`. - **Owner or admin access to the workspace.** A member does not see the tile at all, and a direct link shows a notice saying to ask an owner or admin. If a workspace drops to a lower plan, the key stays in place but every call is refused with `403 TIER_NO_MCP` until you upgrade. Upgrading restores it immediately, with the same key. ## Generate your key 1. Open **Settings** for the site you want (the site's settings, not Account Settings). 2. Click the **Integrations** tab. 3. After **Webhooks**, click the **MCP (AI access)** tile. Its status line tells you where you stand: `No key yet`, `Key active`, or `Key active ยท reads `. 4. If your workspace owns more than one site, choose which one the key reads under **Site this key reads**. 5. Click **Generate key**. The key is shown **once**, together with both ready-made client configs. Copy what you need before closing the dialog: it cannot be recovered afterwards. ### One key, one workspace, one site - There is **one MCP key per workspace**. Generating again replaces it, and the old key stops working immediately, so any client still using it fails until you paste in the new one. - The key is **bound to one site**. No tool takes a site parameter, so your AI can never reach another site: the key decides, not the question. To cover a second site, generate a separate key for it. - **Revoke** cuts off access right away. You can generate a new key afterwards. - The MCP tile is the only place this key is managed; it is not listed with your other API keys. ## Connect your AI client The server is: ``` https://app.truemetriks.com/mcp ``` There are two config shapes and **they are not interchangeable.** Use the one that matches your client, or it will not connect. ### Claude Desktop Claude Desktop speaks stdio only. It has nowhere to put a bearer header, so it needs the `mcp-remote` bridge, which turns our HTTP endpoint into the stdio server it expects. This needs Node installed, since it runs through `npx`. Its config file lives at: ``` macOS ~/Library/Application Support/Claude/claude_desktop_config.json Windows %APPDATA%\Claude\claude_desktop_config.json ``` ```json { "mcpServers": { "truemetriks": { "command": "npx", "args": [ "-y", "mcp-remote", "https://app.truemetriks.com/mcp", "--header", "Authorization: Bearer YOUR_MCP_KEY" ] } } } ``` ### Cursor, Claude Code and other remote MCP clients Anything that can talk to a remote MCP server takes the URL and headers directly, and must **not** be given the bridge form: ```json { "mcpServers": { "truemetriks": { "url": "https://app.truemetriks.com/mcp", "headers": { "Authorization": "Bearer YOUR_MCP_KEY" } } } } ``` Cursor reads `~/.cursor/mcp.json`, or a `.cursor/mcp.json` inside a project. Claude Code reads `.mcp.json` in the project root. Other clients differ, so check your client's own documentation for where its MCP config lives. Replace `YOUR_MCP_KEY` with your key, save, and **restart the client**. Most clients only read their MCP config at startup, so a config change without a restart looks exactly like a broken key. ## What your AI can answer Fourteen read-only tools are exposed. Your AI picks the right one; this list is here so you know what is in reach. | Tool | What it answers | |------|------------------| | `get_overview` | Traffic overview: sessions, pageviews, unique users, bounce rate, session duration | | `get_channel_breakdown` | Acquisition by marketing ecosystem (facebook, google, tiktok, email, direct): visits, visitors, revenue, ad spend, ROAS. Optional paid or organic filter | | `get_campaign_performance` | Per campaign: visits, revenue, spend, ROAS, cost per sale. Best and worst performers | | `get_ad_spend` | Spend from synced ad accounts, by platform or by campaign | | `get_revenue_summary` | Revenue, purchases, average order value, conversion rate and LTV, matching the dashboard's attributed view | | `get_product_performance` | Per product: revenue, orders, average order value | | `get_customer_ltv` | Per-buyer spend distribution: average and median LTV, repeat purchase rate | | `get_buyer_journeys` | Per-buyer lifetime attribution: first touch, last touch, sessions, revenue, days to purchase | | `get_buyer_detail` | The full event timeline for one buyer, by email or visitor id, including their anonymous visits before they identified | | `get_time_to_purchase` | How long buyers take from first visit to first purchase: average and median days | | `get_event_funnel` | Event-to-event conversion per person, for example Lead to Purchase, with average and median hours between | | `get_funnel_ab_results` | Your saved [funnels](../funnels/), including A/B variant splits | | `get_traffic_dimensions` | Top pages or top referrers, top 25 only | | `compare_periods` | Runs another tool over two date ranges and returns both side by side | Every tool takes `start_date` and `end_date` as `YYYY-MM-DD`, except `get_buyer_detail` (a `buyer_id`) and `compare_periods` (a tool name plus two ranges). **No tool takes a site or organization parameter.** The site comes from the key, which is what keeps one workspace's questions from reaching another's data. Some results are capped and say so: `get_traffic_dimensions` returns the top 25, `get_ad_spend` the top 100, `get_buyer_detail` at most 500 events. Each sets a `truncated` flag worth checking before treating a list as complete. ## Limits | Limit | Value | |-------|-------| | Date range per request | 180 days | | Requests per minute | 30 per key | | Requests per day | 2000 per key | | Requests at once | 3 per key | Ask for more than 180 days and the request is refused cleanly, telling the caller to split it into shorter periods. A good AI client does that by itself and stitches the answers together. ## Calling the server directly Most people never need this, but if you are testing by hand, two details matter: - The `Accept` header must list **both** types: `application/json, text/event-stream`. Sending one gets a `406`, which is easy to mistake for a broken key. - Auth is `Authorization: Bearer `. No key gets `401`. ```bash curl -X POST https://app.truemetriks.com/mcp \ -H "Authorization: Bearer $TM_MCP_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' ``` ## When it does not work | What you see | What it means | |--------------|----------------| | `401 AUTH_REQUIRED` | No `Authorization` header reached the server | | `401 INVALID_API_KEY` | The key is unknown, expired or revoked. Regenerating replaces the old key, so an old config fails this way | | `401 NOT_MCP_KEY` | That is an [API key](../api/), not an MCP key. The two are separate and are not interchangeable | | `403 TIER_NO_MCP` | The workspace is not on the Unlimited plan | | `403 MCP_NOT_ENABLED` | MCP is not switched on for this account yet | | `406` | The `Accept` header did not include both `application/json` and `text/event-stream` | | `429 RATE_LIMITED` | Over the per-minute or per-day limit. Wait and retry | | `409 NO_SITE` | The workspace has no site for MCP to read | | `409 AMBIGUOUS_SITE` | The workspace has several sites and the key is not bound to one. Regenerate it and pick a site | | No MCP tile at all | You are a member, not an owner or admin, of this workspace | One more worth checking before you go hunting: a key also stops working if the owner or admin who generated it is later removed from the workspace or demoted to member. Regenerating it under a current admin fixes that. ## MCP or the API? Both read the same data and both are Unlimited-only. **MCP** is for asking questions in an AI client, with no code. The **[v1 REST API](../api/)** is for building something: a scheduled report, a self-refreshing sheet, your own dashboard. Separate keys, so you can use both. # New vs Returning customers Source: https://docs.truemetriks.com/features/new-vs-returning/ See revenue, AOV, and ROAS split by first-time vs repeat buyers, and fire a new-customer-only conversion to your ad platforms to optimize acquisition. Blended revenue hides your most important question: are your ads bringing in new customers, or just collecting repeat purchases you would have gotten anyway? TrueMetriks separates the two everywhere it matters, in the dashboard and out in your ad platforms. **Video guide** ## See it on the Revenue page The [Revenue](../../dashboard/revenue/) page has an **Audience** dropdown with three options: **New + Returning**, **New**, and **Returning**. Switching it re-scopes every number on the page to the audience you pick: total revenue, purchases, AOV, ROAS, and profit, plus every row in the source, campaign, and content breakdowns. Set it to **New** to see what a first-time buyer really costs you and what they are worth on that first order; set it to **Returning** to see the repeat-purchase business your existing customers drive. This is the fastest way to judge whether a campaign is genuinely acquiring customers or just harvesting repeats. ## Send it to your ad platform The dashboard view above is for you. To make your ad platform optimize toward new customers, the platform itself has to know which purchases were new, and you do that by firing a separate conversion event for them. Open **Settings**, then **Tracking**, pick your ad platform, and find **Custom events**. Click **Create**, map the standard `Purchase` event to a custom name, then set **Fire when** to **New customers only** (or **Returning only**). Now that custom event fires only when the buyer is a first-time customer. Your normal `Purchase` keeps firing for everyone, so nothing you already rely on changes; you are simply adding a clean, new-customer-only signal alongside it. ## Use it as a column in your ads Saving the mapping auto-creates the custom event as a **Custom Conversion** on your pixel in Facebook (Events Manager, then Custom conversions). It starts inactive until the first matching purchase lands, then goes active. The full Facebook setup, including creating the catalog and finding the conversion, is on the [Facebook integration](../../ad-integrations/facebook/) page. From there, add it as a column in Ads Manager: open the **Columns** menu, choose **Customize columns**, search the custom name, tick **Total**, **Value**, and **Cost**, and Apply. Now each campaign shows its new-customer purchases and their value right next to spend. Prospecting campaigns that win first-time buyers stop being buried inside blended totals, and you can optimize budget toward the campaigns that actually grow your customer base, not just the ones that re-bill people who would have come back anyway. ## How a customer is counted as new On each purchase, TrueMetriks looks up whether that same person has bought from you before. It matches a person by the user ID your site sends when it identifies them, falling back to their email, and then to their persistent device identity, so a repeat buyer is recognized across sessions and devices. The first purchase it can attribute to a person is counted as new; everything after is returning. New-customer status is scoped to your workspace, so a first purchase from your store counts as new for you regardless of anything that happened on another business's site. ## Import your existing customers first Because this is judged from our own record of who has purchased, it only knows about buyers TrueMetriks has actually seen. If you already had customers before installing TrueMetriks, or customers who bought through a channel it does not track, their next order would be counted as new, because as far as our records show it is their first. **Video guide** Seed that history so it does not happen. Go to [Users](../../dashboard/users/), open the **Imported customers** tab, and click **Upload CSV**. The file needs at least an `email` column; `first_name`, `last_name`, `phone`, `country`, and `first_purchase_date` are optional, and any other columns are ignored. Everyone on that list is treated as an existing customer from then on, so their first purchase after the import counts as returning, not new. This is what keeps your new-customer numbers honest, both in the dashboard and in the new-customer conversions you send out to Facebook, Google Ads, GA4, and TikTok. Import your customer list once, up front, before you start trusting the new vs returning split. # Buyer Journeys and attribution models Source: https://docs.truemetriks.com/features/buyer-journeys-attribution/ Switch between first-touch, last-touch, and any-touch attribution on the Revenue page, and read a single buyer's full ordered path to purchase under Users. When someone takes five visits across five channels before buying, "where did that sale come from?" has more than one honest answer. Attribution models are just the different ways of answering it, and TrueMetriks gives you the three that marketers actually use. ## Attribution models You switch between models with the **Attribution** dropdown. There are three: - **First-touch** credits the whole sale to the channel that first brought the person in. This is your acquisition view: which channels start journeys. - **Last-touch** credits the whole sale to the channel on the visit where the purchase happened. This is your closing view: which channels seal the deal. - **Any-touch** credits the sale to every channel that appeared anywhere in the journey. If a buyer touched Facebook, then email, then Google before purchasing, all three get credit for that purchase. This is the one most marketers want, because it shows every channel that helped, not just the first or the last. (Because one purchase is credited to several channels, the per-channel purchase counts under any-touch can add up to more than your real total; that is expected.) ### Why not linear or time-decay You may have seen two other models elsewhere. **Linear** splits one sale into equal fractions across every touch (a fifth of a sale each, across five touches). **Time-decay** does the same but gives recent touches a bigger fraction than older ones. We deliberately do not use them. The reason is simple: marketers do not think in fractions of a sale. Telling someone "Facebook earned 0.2 of this purchase" is not useful. What they actually want is to see the real sequence: first touch was a Facebook ad, then email, then Instagram, then a Google search, and then they bought. First-touch and last-touch give you the two ends of that story, and any-touch gives you the full cast of channels involved. Fractional-credit models bury that sequence behind math, so we leave them out. ## See revenue by model on the Revenue page The [Revenue](../../dashboard/revenue/) page has the Attribution dropdown at the top. Switch it and every row in the source, campaign, and content breakdowns recalculates to credit purchases and revenue the way that model does. Flip to **First-touch** to see which channels acquire new people, **Last-touch** to see which channels close, and **Any-touch** to see every channel that played any part in your sales. Reading the same period three ways is what tells you, for example, that a channel which rarely gets the last click is actually starting most of your journeys. ## See one buyer's full journey under Users The Revenue page tells you the story across all your traffic. To see one person's path, go to [Users](../../dashboard/users/) and open any user. Their profile lists every visit they have made, in order, so you can follow exactly how they went from first contact to purchase. Each card is one visit, newest at the top, with the channel that drove it and what they did. Reading from the bottom up, you can watch the whole journey unfold: the ad that first found them, the touches that brought them back, and the visit where they finally bought. This matters most when you have long sales cycles or an expensive product, where nobody buys on day one. Blended numbers make it look like whatever channel got the last click did all the work, when really an earlier channel did the expensive job of finding the customer in the first place. Following real journeys lets you give credit to the channels that start them, not just the ones that happen to be there at the finish. # Restricted categories Source: https://docs.truemetriks.com/features/restricted-categories/ Keep optimising on Facebook in restricted categories like health, finance, and supplements by firing your conversion through a neutral custom event name. **Video guide** ## Why standard events get throttled If your business sits in a category Meta restricts (health, wellness, weight loss, finance, supplements, gambling, dating, alcohol, and a few others), Meta will block or throttle ad delivery when you optimise on certain standard event names: `Purchase`, `Lead`, `InitiateCheckout`, `AddToCart`, `Schedule`, and `ViewContent`. In practice you get pushed back to `PageView`, which is too high in the funnel to optimise on, so your ROAS suffers even when the offer converts fine. The workaround is to stop sending the restricted standard event and instead fire your conversion through a custom event with a **neutral, meaningless name**. TrueMetriks sends everything server-side through the Conversions API, so you fully control the name Meta sees. Meta's pattern detector cannot map a random name back to a restricted intent, so your delivery is judged on performance, not on the label. ## Rule 1: give the event an arbitrary name This is the part most people get wrong. The whole point is that the event name reveals nothing. So do **not** name it anything that hints at what it is: - Bad: `custom_purchase`, `purchase_v2`, `real_purchase`, `buy`, `checkout`, `lead_real`, `sale`. Each of these still contains the intent Meta is looking for. - Good: `ghu`, `kt8x2`, `evt_a7`, `kenny_florian`, `bluefin` - a random string or unrelated word that carries no meaning. Meta sees a name it cannot classify, so it has nothing to restrict. Pick something you will recognise in your own reporting but that means nothing to an outside pattern detector. Avoid any of the words `purchase`, `buy`, `cart`, `checkout`, `lead`, `signup`, `subscribe`, `order`, `sale`. A short nonsense token like `ghu` is the safest choice. You still get a clean, dedicated conversion column in Ads Manager and you optimise campaigns against it exactly like a normal Purchase. The only thing that changed is the label. ## Rule 2: keep your product names and URLs neutral too The event name is not the only thing Meta reads. Its systems also scan your **landing page URLs, product names, and on-page content**. If your custom event is called `ghu` but it fires on `yoursite.com/diabetes-destroyer`, you have given the game away - the URL hints at exactly what the offer is. So apply the same discretion everywhere the platform can see: - **Product names / slugs:** use a neutral code, not the marketing name. `Diabetes Destroyer` becomes `Program A` or `offer-7`; `Keto Shred Pro` becomes `bundle-b`. - **URLs:** prefer `yoursite.com/p/offer-a` or `yoursite.com/order/7` over `yoursite.com/diabetes-destroyer`. The path should not describe the niche. - **Anything the pixel or crawler can read** (page titles, content names sent with the event) should stay just as neutral as the event name. The goal is consistency: a meaningless event name plus a neutral URL and product name give Meta nothing to classify. A neutral event name next to a giveaway URL does not. ## Set up the custom event You create the custom event inside the TrueMetriks integration card for your pixel, the same place the **Allowed events** checkboxes live. First, uncheck the restricted standard (for example `Purchase`) in Allowed events so only your neutral custom name fires. Then create the custom event. ### Step 1: click Create Inside the expanded pixel row, scroll to the **Custom events** panel and click the highlighted **Create** button. ### Step 2: fill in the popup with a neutral name The **Create custom event** popup opens. Set: - **Standard event** (dropdown) - the standard event you want to mirror. Pick `Purchase` to fire every time a Purchase would have. Same for `Lead`, `Schedule`, and so on. - **Custom event name** (input) - your arbitrary name from Rule 1, such as `ghu`. This is the name Meta sees. - **Fire when** (dropdown) - `Always` is the default. For `Purchase`, `Lead`, and `Schedule` you can also pick `New customers only` or `Returning only` to split your audience (see [New vs returning customers](../new-vs-returning/)). Click **Create**, then click the highlighted **Save changes** button at the bottom of the card to create the Custom Conversion in Facebook. ### Step 3: confirm the Custom Conversion in Facebook The moment you save, TrueMetriks calls the Meta API and creates a **Custom Conversion** in Facebook for you - you do not set it up by hand. To confirm, open Meta Ads Manager → **Events Manager** → pick the pixel you mapped → **Custom conversions** tab. Your neutral name shows up there. It stays **Inactive** until the first event lands. Fire a test event or wait for live traffic; the status flips to **Active** once Meta sees the first conversion. ### Step 4: add the conversion as a column In Meta Ads Manager → **Campaigns** (or Ad sets / Ads) → the **Columns: Performance** dropdown → **Customize columns**. Search for your neutral name, tick **Total**, **Value**, and **Cost** on the matching row, then click Apply. ### Step 5: read the column and optimise on it The Campaigns / Ad sets / Ads table now has a dedicated column for your custom conversion. Use it exactly like a Purchase column - sort by it, build saved reports, and set automated rules and ad-set optimisation against it. ## Gotchas - **Wait 30 to 60 seconds after Save** before firing test events. Meta needs to finish creating the Custom Conversion first; events fired too early land without the conversion attached. - **Removing a custom event in TrueMetriks does not delete the Custom Conversion in Facebook.** It stops the custom name from firing, but the entry stays in Events Manager. Delete it manually from Facebook's Custom conversions tab if you no longer need it. - **Custom Conversions have a 100-per-pixel limit** (Meta's restriction). Each custom event counts as one; the list shows how many you have remaining. ## Related - [Facebook integration](../../ad-integrations/facebook/) - the full custom events reference and how the Conversions API connection works. - [New vs returning customers](../new-vs-returning/) - use the `When` condition to split a restricted-category conversion by audience. # GDPR compliance Source: https://docs.truemetriks.com/features/gdpr-compliance/ Make TrueMetriks GDPR and CCPA friendly in one click: switch Cookie tracking off to run cookieless, with no IP retention and no persistent visitor identifiers. These two controls live under your site's **Settings**, in the **Privacy & Security** section. Together they decide how visitors are identified and how much personal data is collected. ## Cookie tracking This is the main privacy switch. **ON (default):** Full tracking. The visitor's browser stores cookies, the IP address is recorded, and visitors are recognized across multiple sessions and visits over time. This gives the most complete attribution and reporting. **OFF (compliant mode):** GDPR and CCPA friendly mode. No cookies are stored on the visitor's device, the IP address is not retained, there is no cross-session tracking, and no persistent identifier is kept. Each visitor is counted using an anonymous identifier that resets every day, so no individual can be followed over time. One click on this toggle handles everything: cookies, IP, and persistent identifiers all switch together. Turn it off if you want to run analytics without a cookie banner or consent prompt. ## Block bot traffic This keeps automated traffic out of your reports. **ON (default, recommended):** Traffic from known bots and crawlers is filtered out and never counted, so your numbers reflect real people. **OFF:** Bot and crawler traffic is counted alongside real visitors. This setting is purely about data quality. It has no effect on privacy or compliance, so you can leave it on at all times. ## Turn on compliant mode To make a site GDPR and CCPA friendly: 1. Open the site's **Settings** and go to **Privacy & Security**. 2. Turn **Cookie tracking** off. 3. Leave **Block bot traffic** on. 4. Click **Save changes**. That is it. The site now runs cookieless, with no IP retention and no persistent visitor identifiers. # Chrome Extension Source: https://docs.truemetriks.com/features/chrome-extension/ Install the TrueMetriks Ad Overlay to see attribution-corrected ROAS, Profit, CPL, and CPS on every row inside Facebook Ads Manager and Google Ads.
Add to Chrome → Free to install. Available on the Growth and Unlimited plans.
**Video guide** ## What it does The TrueMetriks Ad Overlay puts your real numbers right next to Facebook's and Google's, on the screen you already work in. It adds a dark TrueMetriks toolbar above your ad table and eight extra cells to every ad, ad set, ad group, or campaign row.
The TrueMetriks Ad Overlay running inside a real Facebook Ads Manager account
The overlay running in a real Facebook Ads Manager account.
Spend is read from the ad platform; every other number comes from TrueMetriks. Each one is calculated for the date range and attribution model you pick in the toolbar, so the ROAS you see is real, not a platform estimate. ## Install it The extension is published on the Chrome Web Store as "TrueMetriks Ad Overlay". 1. Open the [Chrome Web Store listing](https://chromewebstore.google.com/detail/truemetriks-ad-overlay/hfkmfhbidmgfpfnignikimdniaebppdc) and click `Add to Chrome`, then approve the permissions. 2. Pin the extension so the icon stays in your toolbar. 3. Click the icon and choose `Connect to TrueMetriks`. It signs you in with your existing `app.truemetriks.com` login, so there is no separate password. Once connected the popup shows `Connected as you@yourcompany.com`. After that, open **Campaigns**, **Ad sets** or **Ads** to see your numbers. Both platforms work the same way: Blank overlay? You are usually on a page the extension does not cover, or the table has not finished rendering. Refresh and give it a moment. Facebook used to fill in the Ads tab only. It now fills Campaigns and Ad sets too, because a click tagged with an ad's id also counts toward that ad's ad set and its campaign. ## Why a campaign can be bigger than its ad sets added up Add up every ad set inside a campaign and the total can come out smaller than the campaign row itself. That is not a bug. Ids only ever climb. A click tagged with an ad's id counts toward that ad, its ad set and its campaign. A click carrying only a campaign id stays at campaign level forever: it is never pushed down into one of the ad sets underneath, because a campaign has many and picking one to credit would be inventing an answer. So the campaign row holds everything attributed to that campaign, including touches that never named an ad set, while the ad set rows hold only what named them. The gap between the two is real traffic, sitting at the level it was actually identified at. This is how attribution works rather than a platform quirk, so it applies to Facebook campaigns versus ad sets and Google campaigns versus ad groups alike. ## The eight cells The overlay always adds the same eight cells, in this fixed order, regardless of the order you turn them on. | Cell | What it shows | |---------|---------------------------------------------------------------------| | Leads | Leads TrueMetriks attributes to this row for the selected view | | CPL | Cost per lead, which is the platform spend divided by Leads | | Sales | Purchases TrueMetriks attributes to this row | | CPS | Cost per sale, which is the platform spend divided by Sales | | ROAS | Return on ad spend, which is Revenue divided by Spend | | Spend | Spend read directly from the ad platform | | Revenue | Revenue TrueMetriks attributes to this row | | Profit | Revenue minus Spend | ROAS and Profit are colour-coded so good and bad rows jump out: green when a row is winning, red when ROAS drops below break-even or Profit goes negative. ## The toolbar controls Everything is driven from the dark TrueMetriks bar at the top of the page. ### Attribution Switch how credit is assigned between First touch, Last touch, and Any touch. Every cell instantly recalculates for the model you pick. - First touch credits the ad that first introduced the customer. - Last touch credits the ad they clicked right before buying. - Any touch credits every ad that was part of the journey. This is the same attribution choice you have on the TrueMetriks dashboard. See [Buyer journeys and attribution](../buyer-journeys-attribution/) for how the three models differ. ### Audience Filter the numbers by who converted: everyone, only first-time buyers, or only repeat buyers. Set it to `New only` to optimise your prospecting campaigns on fresh customers instead of repeat purchases that would have happened anyway. See [New vs returning customers](../new-vs-returning/) for the full picture. ### Columns Turn any of the eight cells on or off. The count in the toolbar (`Columns: 8`) shows how many are visible. ### Date The overlay reads the date range from the ad platform's own date picker (shown as `Date: maximum`), so change the date in Facebook or Google as usual and the cells follow. Nothing to set here. ### Refresh and the status dot `Refresh` pulls the latest numbers (the overlay also caches for a minute so it stays fast). The dot on the right shows the connection: green for connected, amber while syncing, red if something is wrong. ### Hide cells `Hide cells` collapses the eight TrueMetriks cells so the ad platform's own right-edge columns show again. Click `Show cells` to bring them back - nothing is lost. ## Google Ads: enable the ID columns first (required) Facebook works the moment you open it. Google does not: the overlay matches each Google row by Google's own ID column, which Google hides by default, so until you turn it on there is nothing to match and no numbers show. > **This step is required.** Enable the ID column on all three views - Campaigns, Ad groups, and Ads. Each has its own ID, and skipping one leaves the overlay blank on that view.
The TrueMetriks Ad Overlay running inside a real Google Ads account
The overlay running in a real Google Ads account, with the ID column enabled.
You do this once per view, and Google remembers it. The three views sit in the Google Ads left menu: ### Step 1 - open Modify columns On the view you are setting up, find the toolbar above the ad table and click **Columns**, then **Modify columns**. ### Step 2 - search the ID and add it In the **Modify columns** panel, type the ID for the view you are on into the search box, tick it so it moves into **Your columns** on the right, then click **Apply** at the bottom. ### Step 3 - repeat on all three views Each view needs its own ID. Do the same on the other two tabs: | Google Ads view | Search for | Enable this column | |-----------------|--------------|--------------------| | Campaigns | `campaign id` | `Campaign ID` | | Ad groups | `ad group id` | `Ad group ID` | | Ads | `ad id` | `Ad ID` | Once a view has its ID column on, refresh the page and the overlay's eight cells appear on every row. If you ever see Google rows with no TrueMetriks numbers, the overlay tells you which column is missing with a message like "Enable the Campaign ID column to see TrueMetriks data" - go back to Modify columns for that view and switch it on. ## Why these numbers beat the platform's own The whole reason the overlay exists is that the numbers Facebook and Google show you are usually too low and credited to the wrong ad. - More sales counted. iOS, ad blockers, and cookie limits drop a big share of pixel events; TrueMetriks catches them server-side. - A longer window. TrueMetriks keeps the full journey, so a sale weeks after the click still gets credited to the ad that drove it, instead of expiring on the platform's short window. - Your choice of attribution. First, last, or any touch, recalculated live, instead of the one model the platform picks for you. - One scoreboard. Facebook and Google each only see their own conversions and over-claim; the overlay scores every row the same way, so their ROAS is finally comparable. The result is a per-ad ROAS and Profit you can actually trust enough to scale or cut on. ## Plan availability The Chrome extension is available on the Growth and Unlimited plans. On Starter it installs but the popup shows an upgrade prompt instead of the overlay. Upgrade from `app.truemetriks.com` and it turns on right away. ## Good to know - It is read-only. The overlay only reads your ad screens and pulls the matching TrueMetriks numbers through your own login. It never edits, pauses, or spends anything in your ad accounts. - It works in Facebook Ads Manager (`facebook.com/adsmanager` and `business.facebook.com`) and Google Ads (`ads.google.com`). - It follows your ad platform's light or dark theme automatically. # Cross-domain tracking Source: https://docs.truemetriks.com/features/cross-domain-tracking/ Keep a visitor as one person when your funnel crosses two separate domains you own, so the ad that started the journey still gets credit for the sale.

Read this first: this is not a way to get a second domain for free

TrueMetriks is priced per domain. Cross-domain tracking exists to join domains that are all part of one funnel, so a single customer's journey across them stays one journey. It is not a way to track a second, unrelated website without paying for it.

What it is for. A funnel that genuinely has to span two domains. The common case is an advertorial or review page on its own domain that sends readers to your store on another domain, kept separate on purpose so the recommendation does not read as you recommending yourself. Same offer, same customer, one journey, two domains.

What it is not for. Tracking a different business, brand or offer that happens to be yours. That is a second site, and it needs its own subscription like any other domain you track.

We do enforce this. Accounts found using cross-domain tracking to avoid paying for a domain are banned, with no refund. If you are unsure which side of the line you are on, ask us before you set it up, not after. See why extra domains are charged separately.

If you run an ad to one domain and that page sends the visitor to a second domain you own, those used to be two separate visitors. Cross-domain tracking keeps them as one person. ## The problem this solves The browser scopes the cookie that identifies a visitor to a single domain, and JavaScript cannot get around that. So without this, someone who clicks a Facebook ad to `advertorial.com` and buys at `store.com` looks like two different people: - The purchase is credited to a stranger, not to the visitor who clicked the ad. - The journey becomes two sessions, and the second is attributed to `advertorial.com` as a referral. - The conversion sent back to Facebook, Google or TikTok carries no click id, so the ad platform gets no credit either. Turn it on and you get one session across both domains, credit to the ad that started the journey, and the click id delivered with the sale. ## When you need this (and when you do not) Only for two **separate domains** you own, like `advertorial.com` and `store.com`. It is **not** for subdomains or paths of your own domain. `shop.example.com` and `example.com/checkout` are already one site and already one visitor. The dashboard refuses those anyway: "This is already part of this site, so it is already tracked." ## Plan requirement **Growth and Unlimited.** On Starter the section is locked, with "Available on the Growth and Unlimited plans." in place of the toggle. The extra domain is not a separate allowance: its pageviews count against your normal limit. **Each extra domain in the funnel is a paid add-on.** Growth includes none and Unlimited includes one; every domain after that is $150 a month or $800 a year, added as a line on the subscription you already have. For what that covers and how it lands on your bill, see [why extra domains are charged separately](../../billing/why-extra-domains-are-charged-separately/). ## Where to find it Open **Settings**, pick your site under **Websites**, then the **Tracking** tab. The **Cross-domain tracking** section sits between **Site platform** and **Ad integrations**. Older notes that say Site Settings are out of date; it moved to the Tracking tab. There is deliberately **no status pill** here, because the proof is the Sessions view, not a badge. ## Turn it on It is off by default. 1. Turn on **Track visitors across my other domains**. The **Domains in this funnel** panel appears. 2. Your own domain is already there, greyed and marked **Primary**. 3. Click **+ Add another domain** and type it **bare**: `store.com`, not `https://store.com/`. A leading `www.` is stripped for you. 4. Add a row per domain your funnel uses. Order does not matter and you do not need a row per direction. 5. Click **Save changes**. Past three extra domains the list pages three rows at a time, with **Previous** / **Next**; the **Primary** row stays visible on every page. A domain someone else owns is accepted without complaint. Listing a domain does nothing until your snippet is running on it. ## The one hard requirement: your snippet on every domain **The same snippet from the Tracking tab has to be on every domain you list.** This is by far the most common reason it "does not work". Nothing new to install, no second site, no second CNAME: the exact snippet you already have, whatever each domain is built on. If you are unsure, open each domain and view source. The [tracking code install guide](../../getting-started/install-first-tracker/) covers where it goes. Shopify is the one exception, because a Custom Pixel cannot tag an outgoing link. See [Cross-domain tracking with Shopify](../../platform-installs/shopify-cross-domain/) for the extra line it needs. ## Both directions work There is no "from" and "to". List the domains in any order and a visitor can travel either way, or back and forth, and stay one person. ## Test it yourself Two minutes, and worth doing once. 1. Open your first domain with a fake click id, for example `https://advertorial.com/?fbclid=my_test_1`. 2. Click a real link on the page through to your second domain, not a bookmark or a typed URL. 3. Browse a page there, and buy something if you can. 4. Open [Sessions](../../dashboard/sessions/). **One session containing pages from both domains, still carrying the source it started with** means it works. It is not working if you see **two separate sessions**, or **one session whose source is a Referral from your own other domain**. Both send you back to the snippet check or the saved domain list. ## The `_tmid` parameter Clicking from one listed domain to another flashes a `?_tmid=...` parameter in the address bar for a split second. That is the handoff, and it is stripped as soon as the second domain reads it. It never reaches your reports and needs excluding nowhere. If it **stays** in the address bar, the second domain did not accept it: the snippet is missing there, or that domain is not in the saved list. ## Limits worth knowing - Ordinary links and form submissions carry the handoff. `window.open`, meta refreshes and server-side redirects do not. - A redirector or shortener that drops the query string loses the handoff. - **A password-protected Shopify store breaks it.** Shopify redirects to `/password` and drops the query string. Easy to hit on a development store, where the password is locked on until the store is on a paid plan. - Journeys already split before you turned it on are not repaired retroactively. - Use one address form per domain, either `www.` or not, rather than mixing them. - Internationalised domains have to be entered in punycode (`xn--`) form. ## Troubleshooting **I still see two sessions.** The snippet is missing from the second domain, or that domain is not in the saved list. Check both, in that order. **The source shows as a Referral from my own other domain.** That domain is not in the saved list, so the visit was treated as ordinary referral traffic. Add it and save. **The `_tmid` parameter stays in my address bar.** Same two causes: no snippet on the destination domain, or it is not in the saved list. **My Shopify store is password protected.** Turn the password off; Shopify drops the query string on its redirect to `/password`. See [Cross-domain tracking with Shopify](../../platform-installs/shopify-cross-domain/). **I turned it off. Did I lose anything?** No. Your domains are kept and come back when you turn it on again. If none of that is it, [Common issues](../../troubleshooting/common-issues/) has the general tracking-not-firing checklist. # Why extra domains are charged separately Source: https://docs.truemetriks.com/billing/why-extra-domains-are-charged-separately/ TrueMetriks is priced per domain, so every extra domain in a cross-domain funnel costs $150 a month or $800 a year, added as a line on the subscription you already have. TrueMetriks is priced per domain. Cross-domain tracking lets one dashboard follow a visitor across several domains you own, and each of those is still a domain we track, so each one is priced like one. That is the whole answer. The rest is detail. For how the feature works and how to turn it on, see [cross-domain tracking](../../features/cross-domain-tracking/). ## What the price covers An extra domain is not a switch we flip. It is a second website sending us traffic. - Every pageview, event, session and purchase on it is collected, processed and stored, every day of the year. - Attribution runs over that traffic the same way it runs over your first domain. - Server-side conversions from it are sent on to Facebook, Google Ads, GA4 and TikTok. And you get what you came for: the visitor stays one person. Someone who clicks your ad on `demo-brand.example` and buys on `demo-brand-store.example` is one journey, not two strangers, so the sale credits the ad that started it instead of a referral from your own other domain. ## What each plan includes | Plan | Cross-domain tracking | Extra domains included | Each additional domain | |------|----------------------|------------------------|------------------------| | Starter, $70/month or $400/year | Not available | None | Not available | | Growth, $150/month or $800/year | Yes | None | $150/month or $800/year | | Unlimited, $270/month or $1,600/year | Yes | One | $150/month or $800/year | Growth plus one extra domain is $1,600 a year. Unlimited is $1,600 a year and includes one. Two domains cost the same either way, so you are never penalised for the plan you started on. ## How it appears on your bill One subscription with more than one line on it, like a phone bill with a plan line and an extra-data line. A Growth customer with one extra domain on the yearly cycle sees this on **Settings > Organization > Subscription**: | Line | Amount | |------|--------| | Growth, yearly | $800.00 | | Extra tracked domain, `demo-brand-store.example` | $800.00 | | Total at renewal | $1,600.00/year | One invoice, one charge, one renewal date, the same card. Your plan is **not** charged twice: the $1,600 is your $800 plan plus the $800 domain, renewing together on the date you already have. On the monthly cycle the bill has the same shape, just smaller numbers: | Line | Amount | |------|--------| | Growth, monthly | $150.00 | | Extra tracked domain, `demo-brand-store.example` | $150.00 | | Total at renewal | $300.00/month | Same logic, shorter cycle: the $300 is your $150 plan plus the $150 domain, renewing together every month instead of every year. ## Buying part-way through your billing period You pay the full price for your cycle on the day you buy, with no proration. On the yearly cycle that is the full $800, because you get a full year of tracking on that domain from that moment; on the monthly cycle it is the full $150, for a full month. It then renews alongside your plan, on the same cycle. At checkout only the new domain is charged. Your plan is not re-charged, and the dialog shows a single line, so there is no total to misread as a double charge. ## Removing an extra domain Remove a paid domain from **Settings > Organization > Subscription**, using the **Remove** button on its line, so the tracking and the charge stop together. It is not removed from the Tracking tab. - It keeps tracking until your renewal date, which you have already paid for. Then it stops and your total drops by the add-on price for your cycle, $150 a month or $800 a year. - No refund and no credit for the unused part of the year. - Nothing is deleted. Your funnel setup and all collected data stay exactly where they are. - Changed your mind before the renewal date? Putting it back costs nothing, because the period is paid for. - Once expired, the domain stays in your list marked as not tracking, with a **Resubscribe** button that restores it immediately, settings and data intact. Be careful with one thing: cancelling your **subscription** ends your plan and every extra domain with it. To drop just one domain, use its **Remove** button. ## A paid slot is locked to its domain **A slot belongs to the domain it was bought for, permanently.** It cannot be swapped while active, and it cannot be reassigned after you remove it. A different domain means a new slot at $150 a month or $800 a year. The reason is simple: you are not buying a transferable licence for "one extra domain", you are buying that specific domain being tracked. `demo-brand-store.example` and `demo-brand-vip.example` are two different websites sending two different streams of traffic, so swapping one for the other is just starting to track a new website. Check the spelling before you confirm. Unlimited's one free domain works the same way. You choose which domain it applies to, confirm behind an "are you sure" step, and from then on it is locked too. ## Subdomains are always included Buying a domain covers all of its subdomains at no extra cost. Buy `demo-brand-store.example` and you also get `shop.demo-brand-store.example`, `checkout.demo-brand-store.example` and every other subdomain of it. You never pay twice for the same domain. Worth knowing before you reach for your card: **you do not need cross-domain tracking for your own subdomains at all.** `demo-brand.example`, `shop.demo-brand.example` and `demo-brand.example/checkout` are already one site and tracked as one visitor. The add-on is only for a genuinely separate domain, and the dashboard turns down your own subdomain if you try to add it. Paste a domain however you have it to hand. We strip `https://` and `www.` for you. ## When your free trial ends During a free trial you can add as many domains as you like, at no cost, and nothing is deleted when it ends. Your trial domains are kept and locked, each with its own **Subscribe** button on the Tracking tab, so you buy them one at a time and only the ones you still want. You are never asked to decide this at checkout while choosing a plan. - **Growth.** Every trial domain is kept and locked. Subscribe to any of them for $800 a year each. - **Unlimited.** First pick which single domain your included free slot applies to. That choice is permanent. The rest stay saved and can be subscribed to for $800 a year each. - **Starter.** Cross-domain tracking is not available, so the section is locked. Your trial domains are saved, and upgrading brings them back within reach. While a domain is not tracked, visitors moving between it and your main domain count as two people again, so sales there will not credit the ad that started the journey. ## Still not sure it is worth it? Work out what one attributed sale on the second domain is worth to you, then compare it to $150 a month or $800 a year. If your funnel really does cross two domains, every sale on the second one is currently credited to nobody, so you are optimising your ad spend against incomplete numbers. If it does not cross two separate domains, you do not need this at all. Check [cross-domain tracking](../../features/cross-domain-tracking/) first. Most people who think they need it are looking at their own subdomain, which is already covered. # Common issues Source: https://docs.truemetriks.com/troubleshooting/common-issues/ Fix the most common TrueMetriks tracking problems: tracking not firing, missing ad IDs, rejected Facebook CAPI events, wrong attribution, and trial gating. ## Tracking is not firing at all **Symptom**: dashboard shows zero live sessions; DevTools Network tab shows no requests to `t.yourdomain.com`. **Causes and fixes**: - **Snippet not loaded**: View source on the live page; confirm the `