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
On this page
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.
What a tracking link is
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.
# 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 aboveFour ways to track link clicks
| Method | Where the count happens | Weakest point |
|---|---|---|
| Shortener with stats | The shortener's server | Bot rules differ by vendor |
| UTM tags in Google Analytics 4 | A browser script, then Google | Script blockers and stripped tags |
| Redirect tracker on your domain | The tracker's server | Counts requests, so bots need filtering |
| A handler you build | Your own server | You build the filtering and the reports |
Link shorteners with built-in stats
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.
| Code | Meaning | RFC 9110 section | Heuristically cacheable | For tracking |
|---|---|---|---|---|
| 301 | Moved permanently | 15.4.2 | Yes | Avoid |
| 302 | Found, temporary | 15.4.3 | No | Standard choice |
| 303 | See other | 15.4.4 | No | Works, rarely needed |
| 307 | Temporary redirect | 15.4.8 | No | Works, keeps the method |
| 308 | Permanent redirect | 15.4.9 | Yes | Avoid |
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.
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.
Step 1: give each placement its own link
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.
| Slot | utm_source | utm_medium | utm_campaign | utm_content |
|---|---|---|---|---|
| Newsletter header | weekly-digest | pricing-update-2026-10 | header-banner | |
| Newsletter footer | weekly-digest | pricing-update-2026-10 | footer-link | |
| Podcast notes | devtools-weekly | audio | sponsor-2026-11 | show-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.
| Field | Example value | Where it comes from |
|---|---|---|
| Time | 2026-10-04 09:14:07 UTC | The tracker's server clock |
| Link | news-2026-10-header | The slug in the short URL |
| IP address | 203.0.113.7 | The connection, used for country and city |
| Location | US, Austin | A geolocation lookup on the IP address |
| Device, OS, browser | iPhone, iOS 17, Safari 17 | The user agent string |
| Referrer | linkedin.com | The Referer header, when the sending app provides one |
| Bot flag | false | A 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.
| Source | What it does | Effect on the count |
|---|---|---|
| Slack link expanding | Slackbot-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 minutes | One extra request per URL, repeated at most every 30 minutes |
| Microsoft Defender for Office 365 Safe Links | Scans URLs before delivery and detonates links without a valid reputation in the background | Requests from a scanner before the recipient reads the email |
| Apple Mail Privacy Protection | Downloads remote content in the background through 2 relays, whether or not the recipient engages | Open counts become unreliable, so clicks are the safer email metric |
| Script blockers | Stop the analytics tag from loading | Google Analytics 4 undercounts; a server-side redirect log is unaffected |
| Safari Intelligent Tracking Prevention | Deletes script-created cookies after 7 days without a visit and caps them at 24 hours after tracker-style link decoration | A visitor ID written by JavaScript expires early |
| Safari 17 Private Browsing | Blocks known tracking query parameters in links | Some tracking parameters are stripped before the page loads |
Here is the cleanup on a send to 8,000 recipients, with invented numbers.
| Stage | Requests | Basis |
|---|---|---|
| Logged by the redirect | 1,150 | Everything the server saw |
| Flagged as bots or prefetch | 310 | Scanner and crawler user agents |
| Repeats within 1 hour | 40 | Same IP address and user agent |
| Counted clicks | 800 | 1,150 minus 310 minus 40 |
| Click rate | 10% | 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.
| Parameter | Limit | Where it comes back |
|---|---|---|
client_reference_id | 200 characters | The checkout.session.completed event |
utm_source and the other UTM codes | 150 characters | The 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 days | Days to spare | Credited |
|---|---|---|
| 7 | -11 | No |
| 14 | -4 | No |
| 30 | 12 | Yes |
| 90 | 72 | Yes |
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.
Step 7: read the results per link
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.
| Link | Clicks | Signups | Conversion | First-month revenue | Revenue per click |
|---|---|---|---|---|---|
| Header banner | 620 | 31 | 5.0% | $1,209 | $1.95 |
| Inline link | 340 | 10 | 2.9% | $390 | $1.15 |
| Footer link | 180 | 2 | 1.1% | $78 | $0.43 |
| PS line | 90 | 4 | 4.4% | $156 | $1.73 |
| Channel total | 1,230 | 47 | 3.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.
| Plan | Price a month | Links | Tracked events a month | Revenue figures |
|---|---|---|---|---|
| Free | $0 | 50 | 1,000 | Hidden |
| Starter | $39 | Unlimited | Unlimited | Shown |
| Growth | $99 | Unlimited | Unlimited | Shown |
| Scale | $199 | Unlimited | Unlimited | Shown |
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.
Frequently asked questions
- Any URL built so a system can record its use. One form carries UTM labels in its query string, and the other points at a domain that logs each request before redirecting. Google lists 9 UTM parameters and asks for 3 every time.
- Yes. A standard Google Analytics 4 property reads UTM-tagged links at no cost, YOURLS is free open-source software you host yourself, and TrackRev's Free plan covers 50 links and 1,000 events monthly. Free setups usually give counts without revenue.
- Each tool deduplicates differently. With a 1-hour window, 5 refreshes in 10 minutes count as 1 click in one tool and 5 in a raw log. Google Analytics 4 records 0 for any visit where its script was blocked, and a shortener also logs scanner and bot requests that never run a script.
- MDN's status code pages say search engines pass SEO ranking to the new URL after a 301 and transfer none after a 302. For a guest post or partner page where ranking value matters, ask for a direct link instead. Redirect links suit email, social posts, ads and print.
- As long as the attribution window you set, whatever the cookie lifetime. A visitor cookie can last 365 days while a 30-day window credits only payments inside 30 days of the click, so day 45 goes uncredited.
- No fixed threshold exists, but the arithmetic shows the problem. At 180 clicks one sale shifts conversion by 0.56 points, and at 1,000 clicks by 0.1 points. Hundreds of clicks per link make a fair floor for ranking.

Written by
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