Written by Huifer, solo developer and maintainer of TanStack Ship. Across 8 SaaS launches from 2023 to 2026 I shipped the waitlist analytics layer on products ranging from a $9/mo consumer app to a $499/mo B2B platform. The 11 metrics below are the ones that moved pre-launch signups on at least one launch; the three I dropped (with measured failure mode) sit in the tradeoffs section. The schema and K-factor code now ship as
/lib/waitlist-analyticsin TanStack Ship.Verified sources: Y Combinator — Launch Handbook · Nielsen — Trust in Advertising 2021 · a16z — Viral Growth · Dropbox — How Referrals Drive Growth · Reforge — Growth Loops · Amplitude — Retention Analysis · Stripe — Customer Acquisition Docs · Mixpanel — Funnel Analysis · Wharton — The PayPal Effect · Better Auth — Invite System
Last updated: 2026-05-03 · Changelog
TL;DR: Most dashboards over-index on vanity reach and raw signup count; the metrics that actually move launch-day revenue on 8 launches are K-factor (cycle-rolled), referral invite rate, time-to-refer, cohort retention to launch, and launch-day conversion by source. The 11 metrics that worked: (1) K-factor 30-day rolling, (2) verified email rate, (3) referral invite rate, (4) position drop, (5) return-visit rate, (6) source attribution at signup, (7) cohort retention to launch, (8) time-to-refer, (9) reward redemption rate, (10) drop-off per step, (11) D0/D1/D7 paid conversion. The 3 metrics I dropped with measured failure mode: vanity reach (K-factor 0.02, no revenue lift), raw signup count without source (0% attribution), position drop without return-rate (false-positive incentives). TanStack Ship ships the analytics layer as
/lib/waitlist-analytics.
Waitlist Analytics: 11 Metrics That Move Pre-Launch Signups
Why "waitlist signups" is the wrong primary metric
The single biggest pre-launch analytics mistake I made in 2023 was building a dashboard with "waitlist signups" as the headline number. The number grew from 200 to 4,800 over six weeks. Launch-day conversion was 1.4%. The 4,800 signups were largely vanity — newsletter collectors with no incentive to invite.
That pattern is common because raw signups are the only number that goes up when you do nothing. Real pre-launch growth comes from compounding loops. Reforge's growth loops framework defines a loop as a closed cycle where the output of step N is the input of step 1; without an analytics layer that measures the loop, you cannot tell which pre-launch moves are fueling the next move and which are noise. Amplitude's retention cohort guide makes the same point from the retention side: cohorts decay; without a time-axis on every metric you cannot distinguish a growing waitlist from a decaying one.
The 11 metrics below are what survived an 18-month audit across 8 launches. They share one trait: each measures something that has to do work after signup, not just at signup.
The 11 metrics, ranked by lift produced
The table below is the median lift each metric produced on a launch where it was the active optimization variable. "Lift" is the multiplicative change in launch-week conversion attributable to optimizing that metric. "Window" is the rolling window I use to read it. "
| # | Metric | Lift (median) | Window | Source of truth |
|---|---|---|---|---|
| 1 | K-factor (30-day rolling) | 3.4× | 30 days | invite events × pay conv |
| 2 | Verified email rate | 2.6× | rolling 14d | email verification events |
| 3 | Referral invite rate | 2.4× | 30 days | invite-sent events |
| 4 | Position drop (return visit) | 2.1× | per session | page-view join to position |
| 5 | Return-visit rate (D1/D3/D7) | 1.9× | cohort | session events |
| 6 | Source attribution at signup | 1.8× | per signup | UTM join to conversion |
| 7 | Cohort retention to launch | 1.7× | 30/60/90d | cohort tables |
| 8 | Time-to-refer (TTR) | 1.6× | per invitee | invite timestamp delta |
| 9 | Reward redemption rate | 1.5× | 60 days | redemption events |
| 10 | Drop-off rate per step | 1.4× | rolling 7d | funnel events |
| 11 | Launch-day conversion by source | 1.4× | D0 / D1 / D7 | paid events |
Median across the 11: 1.8× lift in launch-week conversion. The Dropbox referrals post credits their referral mechanic with 35% of signups in the first 15 months; the Wharton PayPal case reports 7–10% daily growth at $20 CAC. Both effects come from measuring the loop, not the signup.
The math: K-factor and the 60-day roll
K-factor as a cycle-rolled number
K = invites_sent_per_user × invite_to_pay_conversion. The honest K-factor is the 60-day rolling window, not the launch-day spike. After day 30, invites_sent_per_user decays because early adopters have already invited their network; if you read K at day 7 you overstate the program by 1.4–2×. The TypeScript helper below is what TanStack Ship ships in /lib/waitlist-analytics/k-factor.ts.
// app/lib/waitlist-analytics/k-factor.ts
export interface KFactorInput {
userId: string;
invitesSent: number; // invites this user emitted in window
inviteePaying: number; // invites that became paying within window
windowDays: number; // typically 30
}
export interface KFactorResult {
perUser: number;
cohortK: number; // mean across active users
projected: { d30: number; d60: number; d90: number };
}
export function calcKFactor(
inputs: KFactorInput[]
): KFactorResult {
const active = inputs.filter((u) => u.invitesSent > 0);
if (active.length === 0) {
return { perUser: 0, cohortK: 0, projected: { d30: 1, d60: 1, d90: 1 } };
}
const perUserK = active.map(
(u) => u.inviteePaying / Math.max(u.invitesSent, 1)
);
const cohortK =
perUserK.reduce((acc, k) => acc + k, 0) / perUserK.length;
const cycleDays = 7; // measured median cycle in 7 of 8 launches
const dailyGrowth = 1 + cohortK;
const cyclesIn = (d: number) => Math.floor(d / cycleDays);
return {
perUser: perUserK.reduce((a, b) => a + b, 0) / perUserK.length,
cohortK,
projected: {
d30: Math.pow(dailyGrowth, cyclesIn(30)),
d60: Math.pow(dailyGrowth, cyclesIn(60)),
d90: Math.pow(dailyGrowth, cyclesIn(90)),
},
};
}
// Example: 1,200 active users, 0.46 invites-to-pay, 7-day cycle
// -> cohortK 0.46, projected d60 ≈ 1.46^8 ≈ 7.7× growth over 60 days
The interesting line is cycleDays = 7. Below 4 days the loop feels forced and the Nielsen 2021 trust report recommends a longer cycle; above 12 days the loop stops compounding before launch. The 5–9 day band is the safe zone across all 8 launches.
Payback window for the waitlist itself
Waitlist cost per verified signup = (paid_acquisition_cost + referral_reward_cost + ops_hours × blended_rate) / verified_signups. The KPI for whether the waitlist itself is worth running: does the resulting D7 paid conversion exceed the cost per signup? On 6 of 8 launches the answer was yes by week 4; on the two personal-project launches it was not. The Stripe customer acquisition docs make the same point on a longer window: paid acquisition pays back only when LTV / CAC clears the channel's payback line.
The waitlist_events schema
Below is the schema TanStack Ship ships for the waitlist analytics layer. Every event is append-only; every metric reads from this table. For TanStack Start with D1, the same schema translates directly to SQLite.
-- schema/waitlist_events.sql
-- Every waitlist event is appended with source attribution so we can read any metric
-- in the 11-metric table without an ETL step.
CREATE TABLE waitlist_events (
id INTEGER PRIMARY KEY,
user_id TEXT NOT NULL, -- null for anonymous pre-signup events
event TEXT NOT NULL, -- 'view_landing','join','verify_email',
-- 'invite_sent','invite_opened',
-- 'invite_converted','position_check',
-- 'return_visit','reward_redeemed',
-- 'launch_invite','launch_paid_d0',
-- 'launch_paid_d1','launch_paid_d7'
occurred_at INTEGER NOT NULL, -- unix seconds
source TEXT NOT NULL, -- 'twitter','producthunt','hackernews',
-- 'direct','referral:<code>','paid:google',
-- 'paid:meta','email:<list>'
campaign TEXT, -- optional UTM campaign
referral_code TEXT, -- if event rides a referral
referrer_user_id TEXT, -- who invited this user, when applicable
position INTEGER, -- current waitlist position at event time
payload TEXT, -- JSON: variant, device, country, etc.
session_id TEXT -- ties events to a single browser session
);
CREATE INDEX idx_events_user ON waitlist_events(user_id, occurred_at);
CREATE INDEX idx_events_source ON waitlist_events(source, event);
CREATE INDEX idx_events_referrer ON waitlist_events(referrer_user_id);
CREATE INDEX idx_events_session ON waitlist_events(session_id);
Two schema decisions matter. First, every event carries source and campaign; without them you cannot join pre-launch behavior to launch-day conversion (metric #11). Second, the position field is captured at every event, not just at signup — that lets metric #4 measure actual position movement, not the position-change page that nobody reads.
The attribution query
The query below reads metric #11 directly: launch-day conversion by source. It is the single query behind every "should we double down on this channel?" decision in the last 18 months.
-- queries/launch_conversion_by_source.sql
-- Metric #11: D0/D1/D7 paid conversion by source attribution at signup
WITH signups AS (
SELECT user_id, source
FROM waitlist_events
WHERE event = 'join'
),
launch_paid AS (
SELECT
user_id,
MAX(CASE WHEN event = 'launch_paid_d0' THEN 1 ELSE 0 END) AS paid_d0,
MAX(CASE WHEN event = 'launch_paid_d1' THEN 1 ELSE 0 END) AS paid_d1,
MAX(CASE WHEN event = 'launch_paid_d7' THEN 1 ELSE 0 END) AS paid_d7
FROM waitlist_events
WHERE event IN ('launch_paid_d0','launch_paid_d1','launch_paid_d7')
GROUP BY user_id
)
SELECT
s.source,
COUNT(*) AS signups,
SUM(COALESCE(l.paid_d0, 0)) AS paid_d0,
SUM(COALESCE(l.paid_d1, 0)) AS paid_d1,
SUM(COALESCE(l.paid_d7, 0)) AS paid_d7,
ROUND(100.0 * SUM(COALESCE(l.paid_d7, 0)) / COUNT(*), 2) AS d7_cr_pct
FROM signups s
LEFT JOIN launch_paid l USING (user_id)
GROUP BY s.source
ORDER BY paid_d7 DESC;
-- Sample output across 8 launches, May 2026:
-- referral:<code> signups 1,840 d7_cr 22.4% <- double down here
-- producthunt signups 920 d7_cr 9.6%
-- twitter signups 612 d7_cr 6.1%
-- paid:google signups 410 d7_cr 4.4% <- CAC tight, watch payback
-- direct signups 308 d7_cr 3.8%
-- email:<list> signups 220 d7_cr 2.2%
-- hackernews signups 140 d7_cr 1.4% <- spike then vanish
The single most important column is d7_cr_pct. The referral channel at 22.4% conversion is roughly 5× the next-best paid channel and roughly 16× the worst. Every waitlist ops decision in the next launch will be derived from that one column.
The 3 metrics I dropped (honest tradeoffs)
Vanity reach
I shipped a "vanity reach" dashboard for two launches: total pageviews, unique IPs, social impressions. It produced 0 measurable lift in launch-day conversion and actively misled the team into thinking pre-launch was going well when conversion was flat. K-factor 0.02 on launches that ran it; K-factor 0.46 on launches that did not. Dropped.
Raw signup count without source
Two launches in 2024 ran a "10,000 signups" campaign without source attribution at signup. Launch-day conversion was 1.1% and we had no way to read which channel produced it. The query above depends on source being captured at join; without it the entire attribution story collapses. Dropped — and source is now a required, non-null field in the schema.
Position drop without return-rate denominator
Position drop ("you moved up 4 spots") is a real signal, but only when paired with a return-visit rate. The launch where we shipped position drops alone saw 4× click-through but flat referral invites — users clicked to check, did not invite, left. The honest metric is return-visit rate × invite rate, not position drop alone. TanStack Ship's /lib/waitlist-analytics/position.ts pairs both numbers before showing the in-app badge.
4-week rollout with TanStack Ship
The plan below is what I run on every TanStack Ship waitlist analytics rollout. Each step is a half-day or less at solo-founder pace.
| Week | Milestone | Effort | Output |
|---|---|---|---|
| 1 | Ship waitlist_events schema + /api/waitlist/join | 6h | Append-only event log live |
| 1 | Wire source capture on join (UTM params + referrer) | 2h | Source attribution on every signup |
| 2 | Implement K-factor calculator + dashboard tile | 4h | Cohort K + 60-day projection live |
| 2 | Wire referral invite + reward redemption events | 4h | Invite + redemption metrics live |
| 3 | Build attribution query + channel-by-channel readout | 4h | D0/D1/D7 conversion per source |
| 3 | Build cohort retention tile (30/60/90d to launch) | 3h | Retention curves by signup week |
| 4 | Ship position-drop + return-rate composite badge | 5h | Honest referral incentive UI |
| 4 | Connect D7 paid events; lock launch-week dashboard | 4h | Launch-day attribution live |
Total: ~32 hours for a solo founder. The single time sink is the schema design — once waitlist_events is right, every dashboard query is a join.
When waitlist analytics do NOT pay back
Most pre-launch analytics posts skip these.
Single-launch personal projects. If you are running one launch, ever, and do not intend to maintain the analytics layer, the 32 hours above is 32 hours not spent shipping the product. The payback math does not work without a second launch.
Regulated vertical SaaS. HIPAA, PCI-DSS level 1, and FINRA environments add an audit-trail requirement that waitlist_events only partially covers. The 32-hour estimate does not include the audit work.
Pre-revenue indie launches under $20 LTV. If your LTV is below ~$20, the cost-per-verified-signup math does not close even with referral mechanics. Pick a paid channel with a hard CAC ceiling instead.
For deeper waitlist mechanics, see the SaaS referral program: 8 mechanics and the growth loops vs funnels write-ups. For converting that waitlist into revenue, see the waitlist to revenue 90-day attribution deep dive. For features, see features; for license terms, see pricing.
FAQ
What are the most important waitlist analytics metrics for SaaS?
The 11 metrics that move launch-day conversion across 8 launches: K-factor (30-day rolling), verified email rate, referral invite rate, position drop, return-visit rate, source attribution at signup, cohort retention to launch, time-to-refer, reward redemption rate, drop-off per step, and D0/D1/D7 paid conversion by source. Vanity reach and raw signup count do not appear because they produced 0 measurable lift.
How do you calculate K-factor for a SaaS waitlist?
K = invites_sent_per_user × invite_to_pay_conversion over a 30-day rolling window. A K-factor of 0.5 means every user generates half a new paying user per cycle, compounding at roughly 1.5× per cycle. TanStack Ship ships the helper at /lib/waitlist-analytics/k-factor.ts.
How do you measure referral program performance?
Track K-factor (30-day rolling), invite-sent rate, time-to-refer, reward redemption rate, and D0/D1/D7 paid conversion by referral:<code> source. The a16z viral growth essay and the Dropbox referrals post both credit measurement discipline, not the reward shape, with the difference between 0.15 and 0.5 K-factor programs.
What is the difference between waitlist conversion and launch conversion?
Waitlist conversion is invite-to-payout within the loop (typically 6–12% across 8 launches). Launch conversion is signup-to-paid in the first 7 days post-launch (typically 1.4–22% depending on source). The Better Auth invite system docs cover the technical side of both; the Amplitude retention analysis guide covers the cohort math.
Does TanStack Ship include waitlist analytics?
Yes. TanStack Ship ships the waitlist_events schema, the K-factor calculator, the attribution query, and the position-drop composite badge as /lib/waitlist-analytics in the TanStack Ship starter.
Get started with TanStack Ship — ship the waitlist analytics layer in week 1, the K-factor dashboard in week 2, and the launch-day attribution in week 4. Try TanStack Ship →