You post a Show HN at 9am, a Reddit thread at noon, and by evening you have 214 visits and 31 signups. Your analytics says 140 of those visits were direct. You know that is wrong, because you were refreshing the thread all day.
Both channels are bad at handing you attribution. Hacker News sends the referrer without a path, so you cannot tell which thread. Reddit traffic arrives with a referrer from the website but often with none from the mobile apps. This guide builds a small classifier so launch-day signups are credited to a channel and a thread.
Tag the link you submit with utm_source and utm_campaign. On landing, classify the visit from the UTM source first and document.referrer second, then send it with OperatorStack.trackEvent("channel_landing", { channel, campaign }). On signup, send a second event with the same channel, so you can compare visits to signups per channel.
What Each Channel Actually Sends
| Signal | Hacker News | |
|---|---|---|
document.referrer on desktop web | Site origin only, no thread path | Site origin, sometimes a subreddit path |
document.referrer from native apps | Often empty | Often empty |
| Your UTM tags | Verify they survive submission | Survive in posts you control |
| Click ID | None | rdt_cid only on Reddit ads |
Treat the referrer as a fallback, never the primary signal. It tells you the platform when it is present and tells you nothing when it is empty, and the empty case is common exactly where launch traffic is heaviest: phones.
OperatorStack's script tag already stores a first-touch os_attribution cookie with utm_source, utm_medium, utm_campaign, and rdt_cid. The full mechanics are in Track UTM Parameters Without Google Analytics. What it does not do is look at the referrer when there is no tag, which is the gap below.
Step 1: Tag Each Link Per Thread
Make one tagged URL per place you post. The campaign names the thread, not the channel.
https://yoursite.com/?utm_source=hackernews&utm_medium=social&utm_campaign=show-hn
https://yoursite.com/?utm_source=reddit&utm_medium=social&utm_campaign=r-saas
https://yoursite.com/?utm_source=reddit&utm_medium=social&utm_campaign=r-indiehackers
After you submit to Hacker News, click your own title link from the listing and read the address bar. If the query string is gone, your tags did not survive and every HN visitor falls back to the referrer path in step 2. Do this in the first minute, while changing the submission is still possible.
Step 2: Classify the Landing
Add this after the OperatorStack script tag. It reads the UTM source first, falls back to the referrer hostname, and only reports direct when both are missing.
<script src="https://operatorstack.dev/os.js" data-project="pk_your_project_key"></script>
<script>
function classifyChannel() {
const p = new URLSearchParams(location.search);
const utm = (p.get("utm_source") || "").toLowerCase();
if (utm) return { channel: utm, campaign: p.get("utm_campaign") || "none", via: "utm" };
let host = "";
try {
host = new URL(document.referrer).hostname.replace(/^www\./, "");
} catch (e) {}
if (host === "news.ycombinator.com")
return { channel: "hackernews", campaign: "none", via: "referrer" };
if (host.endsWith("reddit.com") || host === "redd.it")
return { channel: "reddit", campaign: "none", via: "referrer" };
return { channel: host || "direct", campaign: "none", via: host ? "referrer" : "none" };
}
const landing = classifyChannel();
sessionStorage.setItem("os_channel", JSON.stringify(landing));
OperatorStack.trackEvent("channel_landing", landing);
</script>
The via property is the useful debugging field. If most of your Reddit visits report via: "referrer", your tagged links are not being clicked, and people are finding the thread another way.
Step 3: Carry the Channel Onto the Signup
A landing event tells you who visited. A signup event tells you who converted. Send the stored channel when joinWaitlist() resolves:
async function signUp(email) {
const result = await OperatorStack.joinWaitlist({ email });
const landing = JSON.parse(sessionStorage.getItem("os_channel") || "{}");
OperatorStack.trackEvent("signup_attributed", {
channel: landing.channel || "direct",
campaign: landing.campaign || "none",
});
return result;
}
sessionStorage is enough on launch day because most signups happen in the same visit. For visitors who return later, read the os_attribution cookie instead; it is first-touch and lasts 30 days.
Step 4: Read the Two Events Side by Side
Open Analytics, then Events. Pick channel_landing and read the channel card, then signup_attributed and read the same card. Each card lists top values with counts.
A worked example. Say channel_landing shows hackernews at 120 and reddit at 70, while signup_attributed shows 9 and 14. HN converts at 7.5 percent and Reddit at 20 percent. The thread with more traffic was not the one that produced more signups, and you would have missed that with a single "direct" bucket.
Compare by campaign too. Two subreddits can differ by an order of magnitude, and the answer decides where you post your second launch.
Mistakes That Skew the Numbers
Do not tag links inside the thread comments that point to other pages on your own site. They create a second landing event for a visitor you already counted. Do not reuse one campaign name across threads, or you lose the per-thread breakdown. And do not trust a spike labeled direct on launch day without checking the via property first.
Frequently Asked Questions
Why does my analytics show Hacker News traffic as direct?
Some visitors arrive with an empty document.referrer: native mobile apps, privacy browsers, or tabs opened with a no-referrer policy. With no referrer and no UTM tag, the visit has no source. A tagged link fixes it.
Can I put UTM parameters on a Show HN link?
Yes, but test it. After you submit, click the title link from the HN listing and read the address bar. If your tags survived, keep them. If not, use a path-based tag such as /hn that redirects to your landing page.
How do I tell Hacker News traffic from Reddit traffic in one report?
Classify each landing from the UTM source first and the referrer hostname second, then send the result as a channel property with trackEvent(). The Events tab totals each channel across every page.
Which subreddit sent a signup?
The referrer rarely names the subreddit reliably. Give each post its own utm_campaign, such as r-saas or r-indiehackers, and read the breakdown by campaign.
How long should I keep watching after launch day?
Both channels spike for hours, then trickle for weeks as the thread is found through search. The 30-day first-touch cookie keeps credit with the original channel for late converters.