How to track link clicks: 4 methods, their limits, and matching clicks to sales

4 ways to track link clicks in 7 steps: shorteners, UTM tags, redirects and your own server, plus where bots and Safari skew counts and how to match sales.

Muzahid Maruf — Founder of TrackRev.io

Muzahid Maruf

Link tracking · 10 min read
On this page
  1. 01What a tracking link is
  2. 02Four ways to track link clicks
  3. 03Setting up a tracked link in seven steps
  4. 04Where TrackRev fits

Explore with AI

Opens this article inside the chosen assistant with a ready-made prompt.

To track link clicks, send each click through something that can count it.

A short link or redirect on a domain you control logs the request on the server and forwards the visitor to the destination, while a UTM-tagged URL lets Google Analytics 4 record where the visit came from.

The two combine well: the redirect counts per link, and the tags name the campaign.

Bots, email scanners and Safari distort a raw count, and it says nothing about sales until a click is matched to a payment. I build TrackRev, which does that matching, and a plain shortener is enough for some setups.

Key takeaways

  • Clicks get counted in 4 places: a shortener's server, a UTM-reading analytics script, a redirect tracker, or your own server.
  • Answer tracking redirects with a 302 or 307, because a browser may reuse a cached 301 or 308 without contacting your server.
  • Slack previews, Microsoft Safe Links and crawlers add requests that no person made, so filter before comparing tools.
  • A visitor ID passed through checkout, such as Stripe's 200-character client_reference_id, ties a payment back to its click. I wait for a few hundred clicks per link before comparing rates.

A tracking link, also called a tracking URL, carries enough information for a system to record its use. A tagged URL appends query-string parameters such as ?utm_source=weekly-digest.

A redirect URL points at a tracking domain, which logs the request and answers with an HTTP 302, so it works even when the destination, such as an App Store or Google Play listing, runs none of your scripts.

The two builds
# Tagged URL: the destination carries the labels
https://example.com/pricing?utm_source=weekly-digest&utm_medium=email&utm_campaign=pricing-update-2026-10&utm_content=footer-link

# Redirect URL: the tracking domain logs the click, then forwards
https://go.example.com/news-footer  ->  302  ->  the tagged URL above
MethodWhere the count happensWeakest point
Shortener with statsThe shortener's serverBot rules differ by vendor
UTM tags in Google Analytics 4A browser script, then GoogleScript blockers and stripped tags
Redirect tracker on your domainThe tracker's serverCounts requests, so bots need filtering
A handler you buildYour own serverYou build the filtering and the reports

A shortener such as Bitly gives each destination a short URL and counts requests to it, and YOURLS is the free, self-hosted version with click reports, referrer tracking and visitor geolocation. Counting rules differ by vendor.

Bitly's help article, updated May 21, 2024, says it filters known bots and large-scale abuse, flags IP addresses with an excessive click rate for a while, does not revise past metrics when it learns of a new bot, and is not built to catch click fraud.

Dub counts only the first click from each IP address and user agent pair per 1-hour period, so one campaign can total differently in the two tools.

UTM tags read by Google Analytics 4

UTM tags label a visit so Google Analytics 4 can file it, and its script does the counting.

Google's URL builder page lists 9 parameters, says to always include utm_source, utm_medium and utm_campaign, and warns that values are case sensitive, so Google and google become 2 rows.

A visitor who leaves before the tag fires, or who runs a blocker, never appears.

With outbound click measurement on, Google Analytics 4 also logs a click event, with 5 parameters including link_url and outbound, each time someone clicks a link that leads away from the current domain (Google's measurement documentation).

That covers links on your own pages and cannot see a link placed in an email or a bio. The UTM parameters guide covers naming rules.

Redirect trackers on your own domain

A redirect tracker moves the counting to a server. The visitor requests go.example.com/news-footer, the server records the request and replies with a 302 pointing at the tagged destination.

No script runs in the browser, so extensions that block analytics scripts have none to stop, and a branded domain usually needs a CNAME record at your DNS provider.

RFC 9110 lists 12 heuristically cacheable codes, including 301 and 308 but not 302 or 307, so a browser or proxy can answer a repeat visit to a 301 without contacting your server, and that click is never logged.

A 302 or 307 is reused only when the response carries explicit freshness headers such as Cache-Control: max-age.

CodeMeaningRFC 9110 sectionHeuristically cacheableFor tracking
301Moved permanently15.4.2YesAvoid
302Found, temporary15.4.3NoStandard choice
303See other15.4.4NoWorks, rarely needed
307Temporary redirect15.4.8NoWorks, keeps the method
308Permanent redirect15.4.9YesAvoid

A click handler on your own server

A route on your own site does the same job: look up the slug, write a row, send a 302. This Express-style sketch is pseudocode, so adapt the database calls before running it.

A logging redirect
app.get("/go/:slug", async (req, res) => {  const target = await db.links.findTarget(req.params.slug);  if (!target) return res.redirect(302, "/");  await db.clicks.insert({    slug: req.params.slug,    ts: new Date(),    userAgent: req.get("user-agent"),    referer: req.get("referer"),  });  res.redirect(302, target);});

Your database holds the data and no vendor limit applies, and you write the bot filtering, deduplication, geolocation and dashboards yourself.

In the browser, navigator.sendBeacon() posts up to 64 KiB without delaying navigation, and the ping attribute posts to a URL when a link is followed.

Caniuse shows ping in Chrome 15, Edge 17 and Safari 6, with Firefox shipping it disabled. The server-side versus client-side comparison goes deeper.

One pricing page linked from a newsletter header, a newsletter footer and a podcast description needs 3 links, because the header reaches nearly every reader and the footer reaches only those who read to the end.

A single shared URL merges their data at creation. Stripe's docs give the same advice for Payment Links: utm_content separates links that point to one payment page.

Step 2: tag the destination

Fill in all 4 tags for each slot, in lowercase, with the year and month in the campaign name. The free UTM builder assembles the URL.

Slotutm_sourceutm_mediumutm_campaignutm_content
Newsletter headerweekly-digestemailpricing-update-2026-10header-banner
Newsletter footerweekly-digestemailpricing-update-2026-10footer-link
Podcast notesdevtools-weeklyaudiosponsor-2026-11show-notes

One pricing page, 3 tagged links.

Step 3: put a redirect in front

Create the short link on a domain you control, point it at the tagged URL, use a 302 and confirm the query string survives. Let one tool own the count, because 2 tools counting the same link will disagree.

Step 4: test before you publish

Open the link in a private window and again on a phone over mobile data. The click row should resemble this sample.

FieldExample valueWhere it comes from
Time2026-10-04 09:14:07 UTCThe tracker's server clock
Linknews-2026-10-headerThe slug in the short URL
IP address203.0.113.7The connection, used for country and city
LocationUS, AustinA geolocation lookup on the IP address
Device, OS, browseriPhone, iOS 17, Safari 17The user agent string
Referrerlinkedin.comThe Referer header, when the sending app provides one
Bot flagfalseA user agent and prefetch check

Made-up values for one click.

Step 5: separate people from machines

A redirect log counts requests, including those from software that fetches links before a person does, and several other factors push the count up or down.

SourceWhat it doesEffect on the count
Slack link expandingSlackbot-LinkExpanding 1.0 fetches a URL when someone posts it, reads its Open Graph and Twitter Card tags, and caches the response for about 30 minutesOne extra request per URL, repeated at most every 30 minutes
Microsoft Defender for Office 365 Safe LinksScans URLs before delivery and detonates links without a valid reputation in the backgroundRequests from a scanner before the recipient reads the email
Apple Mail Privacy ProtectionDownloads remote content in the background through 2 relays, whether or not the recipient engagesOpen counts become unreliable, so clicks are the safer email metric
Script blockersStop the analytics tag from loadingGoogle Analytics 4 undercounts; a server-side redirect log is unaffected
Safari Intelligent Tracking PreventionDeletes script-created cookies after 7 days without a visit and caps them at 24 hours after tracker-style link decorationA visitor ID written by JavaScript expires early
Safari 17 Private BrowsingBlocks known tracking query parameters in linksSome tracking parameters are stripped before the page loads

Here is the cleanup on a send to 8,000 recipients, with invented numbers.

StageRequestsBasis
Logged by the redirect1,150Everything the server saw
Flagged as bots or prefetch310Scanner and crawler user agents
Repeats within 1 hour40Same IP address and user agent
Counted clicks8001,150 minus 310 minus 40
Click rate10%800 divided by 8,000 recipients

The unfiltered log would report 1,150 divided by 8,000, or 14.4%.

TrackRev flags known crawler user agents, empty user agents and prefetch requests as bots, still redirects them, and leaves them out of its analytics and attribution.

The bot filtering guide covers the signals, and the Safari ITP post covers the cookie caps.

Step 6: carry the click through to the payment

A click becomes conversion data when something joins its record to the payment record.

The usual key is a visitor ID: the redirect sets a first-party cookie, the signup or checkout step passes the ID along, and the payment event returns with it.

Stripe Payment Links give you 2 places to carry data, described on Stripe's URL parameters page.

ParameterLimitWhere it comes back
client_reference_id200 charactersThe checkout.session.completed event
utm_source and the other UTM codes150 charactersThe confirmation redirect URL, when that behavior is set to redirect

The Stripe Payment Links walkthrough shows the setup.

The attribution window then decides which clicks get credit, and Stripe notes that bank debits and vouchers can take 2 to 14 days to confirm, which adds to the gap between click and payment.

Say a visitor clicks a tracked link on October 1 and pays on October 19.

Window in daysDays to spareCredited
7-11No
14-4No
3012Yes
9072Yes

18 days pass between click and payment.

If the visitor ID sat in a cookie written by JavaScript and the person stayed away for 7 days, Safari deleted it, so a payment on day 18 has nothing to match.

WebKit's 7-day rule covers cookies created in JavaScript, so an ID set in the redirect's response header falls outside it.

WebKit also classifies domains that do enough top-frame redirects as cross-site trackers, and deletes a classified domain's data after 30 days of browser use without a first-party visit.

Per-link figures expose differences that a channel total hides. Take one newsletter edition with 4 links to a $39 a month plan, with invented numbers.

LinkClicksSignupsConversionFirst-month revenueRevenue per click
Header banner620315.0%$1,209$1.95
Inline link340102.9%$390$1.15
Footer link18021.1%$78$0.43
PS line9044.4%$156$1.73
Channel total1,230473.8%$1,833$1.49

Hypothetical figures.

The footer earns $0.43 a click against $1.95 for the header, a gap that the $1.49 channel average hides.

Volume limits how far to trust a row: the PS line reads 4.4% on 90 clicks, and one fewer sale would read 3.3%, so a single payment moves the rate by 1.1 points.

Until each link has a few hundred clicks, I read the channel total and keep collecting per link, since link rows sum into channel totals.

Where TrackRev fits

TrackRev is a link click tracker that runs the redirect method with the revenue join built in. Every tracked link resolves through a /r/slug redirect on a trackrev.io short domain or on your own domain.

The server logs timestamp, referrer, device, browser, OS and city-level location before the 302, and it sets a first-party visitor cookie that lasts 365 days.

Click capture needs no script on your site. Revenue attribution adds one first-party pixel, and on a paid plan it matches each Stripe, Paddle, Polar or Lemon Squeezy charge to the clicks before it, using last-touch, first-touch or linear credit.

The Links and Analytics page has the details.

PlanPrice a monthLinksTracked events a monthRevenue figures
Free$0501,000Hidden
Starter$39UnlimitedUnlimitedShown
Growth$99UnlimitedUnlimitedShown
Scale$199UnlimitedUnlimitedShown

From the pricing page. The 3 paid plans differ on workspaces, commission limits and support.

A plain shortener fits when you need counts on a few links and sell nothing online, and YOURLS or the handler above fits when you want everything on your own hardware. Otherwise start on the free plan.

Found this useful? Share it.

PostLinkedIn

Frequently asked questions

Muzahid Maruf — Founder of TrackRev.io

Written by

Muzahid Maruf

Founder, TrackRev.io & Contant.io

Muzahid Maruf is the founder of TrackRev.io and Contant.io. He writes about marketing attribution, link tracking, and revenue analytics for SaaS teams.

Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth

Stop guessing where your revenue comes from.

Set up TrackRev in about five minutes. The free plan covers 1,000 events a month, no card needed.

Start free