Skip to content
whovisited.

Traffic attribution · Campaign engineering

A practical UTM naming system for small SaaS launches

Build consistent campaign links for a SaaS launch, validate the three UTM fields WhoVisited supports, and compare source sessions with signup goals.

1. Start with a question your launch can answer

You are sharing a SaaS landing page in a newsletter, a product directory and a founder community. A rise in traffic tells you the launch reached someone. It does not tell you which placement brought sessions that completed signup. A small campaign naming system makes those placements comparable without turning every link into a different experiment.

UTM parameters label the incoming link. Source describes where the link was shared, medium describes the channel, and campaign groups the promotion. They are labels you assign, not verified statements about the person clicking. Google Analytics documents these conventions; WhoVisited has its own narrower collection and reporting behavior described below.

Use one concrete goal for the launch, such as signup_completed or an exact success-page path. Keep the goal and landing-page offer consistent across placements if your question is about the source. Otherwise, a different offer and a different source are changing together.

2. Write a three-field dictionary before posting

For a small launch, a plain text file or spreadsheet is enough. Record the final destination, source, medium, campaign, placement, planned date and owner. The placement column is your own operational note, not an extra WhoVisited parameter. Keep it free of private user information.

  1. utm_source: one stable platform or sender label, such as founder-newsletter, product-directory or founder-community.
  2. utm_medium: a short channel label, such as email, directory or community.
  3. utm_campaign: one promotion label shared across placements, such as autumn-launch-2026.

These example labels describe a proposed naming convention, not evidence that any channel performs well. Replace them with names that match your actual distribution. Use lowercase words separated by hyphens as a team rule so you do not accidentally create Founder-Newsletter, founder_newsletter and founder-newsletter as three reporting labels.

text
Destination: https://example.com/launch
Source:      founder-newsletter
Medium:      email
Campaign:    autumn-launch-2026
Placement:   launch email, primary link
Goal:        signup_completed

WhoVisited does not automatically lowercase these values or merge similar spellings. Agree on the dictionary first, then copy existing values when preparing another placement. If you need to change a label after sharing a link, document the change rather than pretending earlier sessions were relabeled.

3. Generate links with validation instead of string concatenation

The current WhoVisited tracker reads utm_source, utm_medium and utm_campaign. Each nonempty value must be at most 100 characters and contain letters, numbers, spaces, underscores, periods or hyphens. Other UTM keys, including utm_content and utm_term, are not collected as campaign fields by this tracker. Do not build a creative-level report around parameters the product does not store.

This generator applies a deliberately stricter lowercase naming convention and requires all three fields. It is a small local utility, not a change to WhoVisited. It accepts only an HTTPS destination without embedded credentials and keeps existing unrelated query parameters and the fragment.

javascript
function campaignUrl(destination, { source, medium, campaign }) {
  const url = new URL(destination);
  if (url.protocol !== "https:" || url.username || url.password) {
    throw new Error("Use an HTTPS destination without credentials");
  }
  for (const [key, value] of Object.entries({ source, medium, campaign })) {
    if (
      typeof value !== "string" ||
      value.length > 100 ||
      !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)
    ) {
      throw new Error(`Invalid ${key}: use lowercase words and hyphens`);
    }
    url.searchParams.set(`utm_${key}`, value);
  }
  return url.href;
}

const link = campaignUrl("https://example.com/launch?lang=en#signup", {
  source: "founder-newsletter",
  medium: "email",
  campaign: "autumn-launch-2026",
});
console.log(link);

URLSearchParams.set replaces an existing value for the same key, including duplicate entries, instead of appending another ambiguous UTM value. This utility returns a URL string; it does not visit the destination or send analytics data. Test it locally before copying the output into your email or directory listing.

4. Preserve the label through the actual distribution path

Test the link that a visitor will receive, not just the string in your campaign sheet. A short link, email click-tracking service or redirect can lead to a final page without the original parameters. Follow the complete route in your own browser and inspect the address of the page where the tracker starts.

  1. Open the shared URL and confirm the final destination is your intended public HTTPS page.
  2. Confirm all three UTM values are present in its query string when tracking begins. Parameters inside a fragment such as #utm_source=email are not read as query parameters.
  3. Check that the correct website snippet is installed on that landing page and collection is enabled.
  4. Complete the same success action you intend to measure; a link click alone is not a signup.

Keep the campaign destination separate from internal navigation. This convention is for incoming links you control. Tagging every menu link inside your own website makes the labels harder to interpret and does not create a dependable new acquisition touch. Avoid customer email addresses, access tokens or other personal data in campaign values; shared URLs can appear in browser history and other systems.

5. Understand when WhoVisited fixes the session source

The tracker reads supported campaign parameters from the current page when creating an event. On the server, a new session stores the campaign, referrer origin and landing path from its first accepted event. Later events in that session update its last-seen time; they do not overwrite those acquisition fields.

For example, a browser starts on an untagged page and then opens a tagged launch link while the same session is still active. You should not expect that existing session to become a new campaign row. Likewise, adding UTM values after collection has already begun is not a clean attribution test.

The browser tracker keeps session state in sessionStorage and refreshes its activity timestamp when tracking runs; it creates a new session after more than 30 minutes without tracked activity. For a controlled campaign test, use a separate test browser context that meets your actual consent requirements. Privacy signals and blockers can still prevent collection. Do not change production tracking to override them.

A recorded source describes a session. It is not a multi-touch journey across devices or proof that the campaign caused a purchase. People can copy a tagged link into another channel, carrying its original label with them.

6. Verify the report with a controlled example

Create a goal for the correct website in Settings, then use Growth with that website, goal and date range. When utm_source is available, WhoVisited builds a source label from it and the optional medium, and groups the row with the campaign value. A consistent three-field link helps you recognize the intended row.

Without a stored source value, the report falls back to supported search-engine labels or a referrer hostname, then Unknown source. A campaign parameter by itself does not establish a source. The dashboard is reporting what it observed; it cannot recover the label that a redirect removed.

  1. Use one deliberately tagged test visit and note the time and timezone.
  2. Confirm an event was accepted and appears in the matching website’s visitor journey.
  3. Complete the goal action and refresh Growth with the correct date range.
  4. Look for your source/medium label and campaign, checking sessions and conversions together.

A goal counts a matching session once even if it contains several matching events. Your account database remains the source for total accounts created. If the test is missing entirely, follow the tracker troubleshooting article before renaming campaign labels or assuming the source report is wrong.

7. Keep the launch review small enough to act on

Review the same goal across placements after enough activity has accumulated. A source with many sessions and few conversions suggests a different next step from a source that barely sent any recorded traffic. Small samples are noisy; a single conversion should not decide your whole distribution budget.

The Growth source table currently shows at most 100 grouped rows. Creating a unique campaign for every individual click fragments the report and can push useful comparisons out of that table. Keep stable labels for meaningful launch placements, and record operational detail in your own sheet instead.

At the end of the launch, save the links actually used and write down one next experiment: improve a landing-page explanation, try another relevant placement, or verify the signup flow. A naming system earns its place when it turns a traffic spike into a question you can investigate, not when it generates more labels than your team can review.

References and further reading

  1. Google Analytics: campaign URL parameters
  2. MDN: URLSearchParams.set()
  3. WhoVisited traffic sources and conversion goals
  4. Debugging missing WhoVisited events