How-to

How to Track Conversions Without Google Analytics 4

Track button clicks, successful form submissions, signups, purchases and funnels with goals and verified payments, and know what each number proves.

Last reviewed 15 min readBy the SuprAnalytics team

You launched a product, a landing page or a service website. People are visiting. What you actually want to know is simpler than any analytics dashboard: are they signing up, getting in touch, or paying? And you'd rather not spend an afternoon configuring Google Analytics 4 to find out.

Conversion tracking doesn't need GA4. Any website analytics tool that can record a named action, match it to a visit and count it can do the job. In SuprAnalytics that means goals (named events or page visits), funnels built from them, and payments confirmed by your payment provider.

The quick answer

  1. Install one analytics script on every page.
  2. Decide which actions count, and keep them apart: a click, a successful submission or signup, and a confirmed payment are different things.
  3. Count clicks with a supr-goal attribute, and successes with window.supr("goal_name") only after your server says the action worked.
  4. Use a thank-you page as a no-code goal when you have one, knowing a pageview isn't proof of a backend transaction.
  5. Count purchases from your payment provider (Stripe, Polar, Lemon Squeezy, Paddle, Dodo Payments) or the Payment API, never from a checkout button.
  6. Put the steps in a funnel, read the conversion rate with its denominator in mind, and test every goal before you trust it.

What counts as a website conversion?

A conversion is any action you decide matters to your business. Google Analytics 4 calls these key events; other tools call them goals. The name matters less than being precise about what each one proves.

Kinds of conversion and what each one proves
ActionWhat kind of signalWhat it proves
Clicked “Start free trial”IntentSomeone clicked. Not that they finished signing up.
Viewed the pricing or contact pageIntentInterest, nothing more.
Contact or quote form accepted by your serverCompleted actionAn enquiry reached you.
Account createdCompleted actionA new user exists in your database.
Newsletter subscription confirmedCompleted actionAn address was added to your list.
Demo request savedCompleted actionA request is waiting for you.
Payment confirmed by Stripe, Paddle…Verified paymentMoney changed hands (until a refund).
Subscription renewalVerified paymentA customer paid again.

All three kinds are worth tracking. The mistake is mixing them up: reporting 120 “signups” that are really 120 clicks on a signup button, of which 70 became accounts.

How conversion tracking works

Most conversions follow the same path: something happens on your website, a tracking event is sent, it's counted as a goal, and it shows up in a report.

Goals: actions on your website

  1. Visitor
  2. Action on your website
  3. Tracking event
  4. Goal
  5. Conversion report

Payments: confirmed by the provider

  1. Visitor
  2. Checkout
  3. Payment provider
  4. Verified payment event
  5. Revenue report

Payments take a different road. The browser can tell you someone reached checkout, but only the payment provider knows whether the card went through, was declined or was later refunded. So revenue comes from the provider, through a webhook or an API call from your server, and is then matched to the visitor who paid. Most conversions don't need any of that: a signup or an enquiry is fully tracked with goals.

Step 1: Install website analytics

Add your website in SuprAnalytics and paste its script into the <head> of every page. Only supr-website-id is required; your website's id is under Settings → General.

index.html
<script
  defer
  supr-website-id="YOUR_WEBSITE_ID"
  supr-domain="yourdomain.com"
  src="https://supranalytics.app/js/script.js"
></script>
  • There are step-by-step guides for Next.js, React, Vue, WordPress, Webflow, Shopify, Wix, Squarespace, Google Tag Manager and more in the installation docs.
  • Single-page apps are covered: route changes made with the History API count as pageviews. Hash routers (/#/pricing) need supr-hash="true".
  • To check it works, open your website with the browser's developer tools on the Network tab: script.js loads and a request goes to /api/event. The dashboard's Online count, which refreshes every 15 seconds, picks up the visit.

Step 2: Track button clicks

A click on a call to action is the easiest thing to track and the easiest to over-read. Add supr-goal to the button or link; every supr-goal-* attribute becomes a property of the goal (dashes turn into underscores).

HTML
<!-- Counts a click. It doesn't prove a trial or a demo booking happened. -->
<a href="/signup" supr-goal="start_trial_click" supr-goal-plan="pro">
  Start free trial
</a>

<button
  type="button"
  supr-goal="book_demo_click"
  supr-goal-location="pricing"
>
  Book a demo
</button>

The click is recorded before the browser leaves the page: the script listens before the link is followed and sends with fetch's keepalive option, which MDN describes as not aborting the request when the page unloads. From your own JavaScript, the same goal is one call:

JavaScript
document.querySelector("#download-guide").addEventListener("click", () => {
  window.supr?.("download_guide_click", { guide: "pricing-checklist" });
});

You don't have to create the goal first: a new name appears in the Goal panel as soon as it arrives. Names are lowercased, spaces become underscores and they're cut at 64 characters, so “Start Trial” and start_trial are one goal. A goal carries up to 10 properties (keys up to 32 characters, values up to 255).

Step 3: Track successful form submissions

The submit button is clicked by people whose form then fails validation, whose network drops, or whose request your server rejects. SuprAnalytics doesn't watch forms on its own, which is deliberate: only your code knows whether a submission succeeded. So send the goal from the success branch, after the server has answered.

contact-form.js
// Your own form and endpoint: /api/contact, showError and showThanks
// are placeholders.
const form = document.querySelector("#contact-form");

form.addEventListener("submit", async (event) => {
  event.preventDefault();

  let res;
  try {
    res = await fetch("/api/contact", {
      method: "POST",
      body: new FormData(form),
    });
  } catch {
    showError("We couldn't send your message. Please try again.");
    return; // network error: no goal
  }
  if (!res.ok) {
    showError("We couldn't send your message. Please try again.");
    return; // the server refused it: no goal
  }

  // Only now has the enquiry arrived.
  window.supr?.("contact_form_submitted", { form: "contact" });
  form.reset();
  showThanks();
});

The same pattern works for a quote request, a demo request or a newsletter signup: wait for the confirmation, then call window.supr() once.

  • window.supr?.() with the ?. means nothing breaks when an ad blocker stopped the script from loading.
  • The call returns nothing and never throws, so there's no need to await it or catch its errors. It also never tells you whether the goal was recorded; check that in the dashboard (see verification).
  • If your code may run before the script has loaded, add the documented queue stub first. Calls made earlier are sent once the script is ready:
Queue stub (optional)
<script>window.supr=window.supr||function(){(supr.q=supr.q||[]).push(arguments)}</script>

Step 4: Track SaaS signups

Three things are often called a signup, and only one is a new user:

  • A visit to /signup: someone looked at the form.
  • A click on “Create account”: someone tried.
  • An account created by your backend: the conversion.

Here's a fictional Next.js form that records the goal only once /api/signup has created the account:

app/signup/SignupForm.jsx
"use client";
// A Next.js signup form. /api/signup is your own endpoint:
// it creates the account.
import { useState } from "react";
import { useRouter } from "next/navigation";

export default function SignupForm() {
  const router = useRouter();
  const [error, setError] = useState(null);
  const [sending, setSending] = useState(false);

  async function onSubmit(event) {
    event.preventDefault();
    if (sending) return; // one submission at a time: no double goals
    setSending(true);
    const res = await fetch("/api/signup", {
      method: "POST",
      body: new FormData(event.currentTarget),
    });
    if (!res.ok) {
      setError("That didn't work. Please check your details.");
      setSending(false);
      return; // no account: no goal
    }
    const { userId, email, plan } = await res.json();
    window.supr?.("signup", { plan });
    // Optional: lets a later payment with this email be matched
    // to this visitor.
    window.supr?.("identify", { user_id: userId, email });
    router.push("/onboarding");
  }

  return (
    <form onSubmit={onSubmit}>
      {/* email, password… */}
      <button type="submit" disabled={sending}>Create account</button>
      {error && <p role="alert">{error}</p>}
    </form>
  );
}

The identify call is optional. It attaches your user id and email to this visitor, which lets a later payment carrying the same email be credited to them. It doesn't merge visitors across devices: the same person on a phone and a laptop is still two visitors. See user identification.

From your server instead

If the account is created in a background job, after an email confirmation, or anywhere the browser can't see, record the goal from your server with the goals API. It needs a website API key (Settings → API, starting with pk_) and the visitor's id from the supr_visitor_id cookie the script sets on your domain.

server.js
// Node.js, on your server, right after the account has been created.
// The API key stays on the server: never put it in browser code.
// The supr_visitor_id cookie is set by the tracking script on your domain.
const visitorId = req.cookies.supr_visitor_id;

if (visitorId) {
  const res = await fetch("https://supranalytics.app/api/v1/goals", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUPRANALYTICS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      visitor_id: visitorId,
      name: "signup",
      props: { plan: "pro" },
    }),
  });
  // 200 { ok: true }. 404 "Visitor has no sessions yet": this id has no
  // tracked visit, so nothing was recorded.
}
  • The visitor must already have a tracked visit: an id with no visits gets 404 Visitor has no sessions yet and nothing is recorded.
  • The goal is attached to their latest visit, at the time your request arrives; it can't be backdated.
  • In cookieless mode there's no cookie: pass window.supr.visitorId() from the page to your server instead.
  • The API allows 3,000 requests a minute per key and has no CORS, on purpose: it's not meant to be called from browsers.
  • Send each signup from one place. A browser goal and a server goal with the same name for the same signup count twice.

Step 5: Track conversions with thank-you pages

If a successful action always lands on its own page, such as /thank-you, /signup/success or /contact/received, you can count that page as a goal with no code at all.

  1. Open the dashboard's Goal panel (or the Goals page) and click +.
  2. In Add a goal, give it a name (optional), choose Pageview, and enter the path.
  3. Click Add goal.
The Add a goal dialog in SuprAnalytics on a fictional website, Launchpad: the name Contact form received, Pageview selected, the path /contact/received and the Add goal button, with the note “Counts a visit to the page, like a thank-you page. Use * as a wildcard.” Not saved. Made-up data.
The real Add a goal dialog, filled in for a thank-you page.
  • The path matches exactly, or with * as a wildcard that matches anything: /signup/*/success, /thanks*.
  • Visits are stored without their query string or trailing slash, so a visit to /thank-you?plan=pro counts as /thank-you. The goal's path is used as you type it: enter it without a query string or trailing slash, or it will never match.
  • Page goals count past visits too, so the numbers appear straight away.

Step 6: Track purchases and payments

A click on “Buy now” or a visit to the checkout page is not a purchase. Cards get declined, people abandon checkout, and some payments are refunded. The reliable record is the one your payment provider sends after it has taken the money.

SuprAnalytics doesn't process payments; it reads them from the provider you already use:

  • Stripe, Polar, Lemon Squeezy, Paddle and Dodo Payments, through a signed webhook (set up in Settings → Revenue);
  • anything else through the Payment API, called from your server once your system has confirmed the payment.

To credit a payment to the visits that led to it, the provider needs to know who the visitor was. With Stripe, pass the supr_visitor_id cookie as metadata on the Checkout Session (Polar checkout links, Dodo payment links and Paddle checkout buttons are tagged by the script itself):

checkout.js
// Node.js: pass the visitor's id to Stripe when you create the Checkout Session.
const session = await stripe.checkout.sessions.create({
  mode: "subscription", // or "payment"
  line_items: [{ price: "price_123", quantity: 1 }],
  success_url: "https://yourdomain.com/thanks",
  metadata: {
    supr_visitor_id: req.cookies.supr_visitor_id,
    supr_session_id: req.cookies.supr_session_id,
  },
  // Subscriptions: renewals' invoices read the subscription's metadata.
  subscription_data: {
    metadata: { supr_visitor_id: req.cookies.supr_visitor_id },
  },
});
res.redirect(303, session.url);
  • Matching: by the visitor id in the payment's metadata, then by your customer id, then by email. A payment that matches no visitor still counts in your revenue, without a source.
  • Renewals are marked as renewals and credited to the customer's original visitor.
  • Refunds: a full refund marks the payment refunded with every provider. Partial refunds differ: ignored for Stripe, lowering the amount for Polar, Paddle and Dodo Payments, and marking the whole payment refunded for Lemon Squeezy.
  • Goals and payments are separate. A goal never carries revenue and never becomes a payment. To compare, each goal shows the revenue of visitors who completed it before paying.
The referrer report of a fictional website, Launchpad, sorted by revenue: Google, direct visits, ChatGPT, a newsletter, LinkedIn and X, each with visitors, bounce rate, session duration, customers, conversion rate, revenue and revenue per visitor. Made-up data.
Revenue by referrer, from verified payments.

Step 7: Build a conversion funnel

A goal tells you how many people converted. A funnel tells you where the others stopped. Take a fictional SaaS:

Example funnel

  1. Landing page
  2. Pricing page
  3. Signup goal
  4. Payment

In SuprAnalytics, open the Goal panel's Funnel tab, click New funnel, and add 2 to 8 steps. Each step is one of:

  • Page visit: the path equals, contains, starts with, ends with, doesn't equal or doesn't contain a value, or matches a wildcard; optionally on one hostname.
  • Goal: the visitor completes a goal, or does NOT complete it (to ask “who saw pricing but didn't sign up?”).
  • Payment: the visitor paid, refunds excluded.

How the numbers are worked out changes what they mean:

  • Funnels count unique visitors, not visits or events.
  • Steps happen in order, and can span several visits: each step counts the visitor's first match at or after the previous step.
  • The first step has to happen in the dashboard's date range; later steps can happen up to the end of it. A visitor who saw pricing on the last day may simply not have paid yet. There's no separate conversion window to set.
  • The dashboard's filters apply, so you can compare the same funnel for Google visitors and newsletter visitors.
  • The headline conversion rate is the last step's visitors divided by the first step's.
A funnel on a fictional website, Launchpad, on a phone, with visitors and the drop-off at each step. Made-up data.
A funnel ending in a payment step.

Funnels show where people drop off; they don't show why. Pair them with visitor journeys to read the pages individual visitors opened before stopping. More detail on the conversion funnels page.

How to calculate conversion rate

Conversion rate = conversions ÷ eligible visitors × 100

A fictional example: 1,000 visitors in a month, 40 of whom completed the signup goal. 40 ÷ 1,000 × 100 = 4% visitor conversion rate.

The answer depends on what you divide by, and products don't all divide by the same thing:

How the denominator changes a conversion rate
DenominatorSame month, fictional numbersRate
Unique visitors40 converting visitors ÷ 1,000 visitors4.0%
Visits (sessions)40 ÷ 1,450 visits2.8%
Funnel entrants40 ÷ 320 visitors who reached pricing12.5%

In SuprAnalytics:

  • A goal's conversion rate is the visitors who completed it ÷ all unique visitors in the period, under the same filters. Per visitor, not per visit.
  • A goal shows two counts: conversions (every completion, so one visitor can add several) and visitors (each person once).
  • The dashboard's own Conversion rate figure is something else: new paying customers ÷ visitors.
  • A funnel's rate uses its first step as the denominator.
The Goals page of a fictional website, Launchpad, on a phone: 7 goals, 11k total conversions and a 10.1% average conversion rate, and a chart of the visitors who completed the viewed_pricing goal each day, with every goal listed under it by visitors. Made-up data.
Goals with conversions, conversion rate and top source.

How to attribute conversions to traffic sources

Every visit gets a source when it starts:

  • Referrer: the website that sent the visitor, grouped into channels such as Organic Search, Social, AI, Email, Referral and Paid.
  • UTM parameters: utm_source, utm_medium, utm_campaign, utm_content, utm_term (and ref), read on the first page of a visit. A tag wins over the referrer. See UTM tracking.
  • Landing page: the entry page of the visit.
  • Direct: no referrer and no tag. Many visits that aren't really direct end up here: links in apps and messages, bookmarks of a page first found elsewhere, browsers that hide the referrer.

Goals and payments are attributed differently, and it's worth knowing which you're looking at:

How goals and payments are attributed in SuprAnalytics
GoalsPayments
Credited toThe visit the goal was completed inThe visitor's first visit or the last one before paying, set per website
Where to see itEach goal's top sources and pages; or filter any report by a source and read the Goal tabRevenue on every source, campaign, page and country row
Can it be Direct?Yes, if that visit was directYes: last touch doesn't skip a direct return visit
NeedsThe tracking scriptA matched visitor (metadata, customer id or email)

Attribution follows a browser, not a person. The script's cookie lasts a year; in cookieless mode a visitor is recognised for a day. Different devices are different visitors, and a payment that can't be matched has no source at all. See revenue attribution for first and last touch in detail.

How to verify conversion tracking

Never trust a goal you haven't watched arrive. Work through this list for each one:

  1. Confirm the script loads

    Network tab: script.js and a request to /api/event. Not on localhost without supr-allow-localhost, not in an automated browser.

  2. Create or confirm the goal

    Event goals appear when the first event arrives; page goals are added with + → Pageview.

  3. Trigger the action yourself

    Submit the real form or create a test account, in a normal browser window.

  4. See the event arrive

    Click Online on the dashboard: the activity feed streams pageviews, goals and payments as they happen. In the Network tab, the request to /api/event should answer 200 or 202, but excluded traffic gets a 202 too, so the dashboard is the real check.

  5. Check the right report

    The goal shows in the Goal panel under the name you expect (lowercased, spaces as _), with its properties.

  6. Test failures

    Submit an invalid form, or block /api/contact in the developer tools. No goal should arrive.

  7. Check for duplicates

    Submit once, reload the success page, press back. Count what arrived: one completion, or several?

  8. Check attribution separately

    Open your site from a tagged link (?utm_source=test) in a private window and convert: the goal's top sources should show it. For payments, make a test purchase in your provider's test mode and check it's attributed.

Your own visits count like anyone's. Once you're done testing, exclude your browser (open your site with #supr-ignore at the end of the address) or your IP under Settings → Exclusions, so tests don't inflate the numbers.

Common conversion tracking mistakes

Counting clicks as completed signups
Name click goals as clicks and track the created account separately.
Tracking failed submissions
Send the goal from the success branch only, after the server answers.
Duplicate events
The browser API doesn't de-duplicate: every call is a completion. Disable the button while submitting, and send each conversion from either the browser or the server, not both.
Inconsistent goal names
Names are lowercased and spaces become underscores, so “Signup” and “signup” are one goal, but “Sign up” (sign_up), “sign-up” and “signup” are three. Pick a convention and keep a list.
A missing script
A page without the script records nothing, including the goal you call on it. Check every template, including the success page.
Misconfigured pageview goals
A trailing slash, or a path that includes the query string, never matches. Use the stored form: /thank-you.
Treating checkout visits as purchases
Count payments from your provider; a checkout page view is intent.
Ignoring visitor matching
Without the visitor id in the checkout's metadata, or an email to fall back on, revenue has no source.
Confusing events with converting visitors
1,200 conversions might be 800 people. Read the visitors count when you mean people.
Assuming Direct means no prior source
Many Direct visits came from an app, a message or a bookmark. With first touch, the original source may still be credited for revenue.

Related: goal tracking, custom goals docs, scroll goals, and how SuprAnalytics compares with Google Analytics.

Frequently asked questions

Can I track conversions without Google Analytics?

Yes. Any analytics tool that records named events or page visits can count conversions. In SuprAnalytics you install one script, then count goals from clicks, successful submissions, signups and thank-you pages, and payments from your payment provider.

How do I track button clicks without GA4?

Add supr-goal="name" to the button or link, or call window.supr("name") in a click handler. Every supr-goal-* attribute becomes a property. A click counts intent, not a completed action.

Can I track contact form submissions?

Yes, but not automatically: forms aren't detected on their own. Call window.supr("contact_form_submitted") after your server confirms the submission, or make a pageview goal for a thank-you page the form redirects to.

How do I track SaaS signups?

Send a signup goal once the account is created: from the browser after your signup endpoint succeeds, or from your server with the goals API, a website API key and the visitor's supr_visitor_id cookie. A visit to the signup page or a click on its button isn't a signup.

Can I track conversions using thank-you pages?

Yes. Add a Pageview goal with the page's path, exactly or with * as a wildcard. It needs no code and counts past visits, but every reload or revisit is another completion, and a pageview doesn't prove a backend transaction.

How do I track purchases without Google Analytics?

Connect Stripe, Polar, Lemon Squeezy, Paddle or Dodo Payments, or send payments from your server with the Payment API. Revenue then comes from confirmed payments, matched to visitors by the visitor id in the checkout's metadata, a customer id or an email.

What is the difference between a goal and a conversion funnel?

A goal counts one action. A funnel is 2 to 8 ordered steps (page visits, goals and payments) and shows how many unique visitors reached each step and where the rest dropped off.

Can I see which traffic sources generate conversions?

Yes. Each goal lists the top sources of the visits it was completed in, and every report can be filtered by source or campaign. Revenue is credited to the visitor's first or last visit. Payments that can't be matched to a visitor have no source.
All articles

Track the actions that matter on your website.

Measure signups, inquiries, goals, funnels, and supported payment revenue without building complicated analytics reports.

14-day free trial · No card requiredPricing