Waitlist Analytics: 11 Metrics That Move Pre-Launch Signups

11 SaaS waitlist analytics metrics that move pre-launch signups across 8 launches. K-factor math, dashboards, honest tradeoffs. Try TanStack Ship →

Huifer
Huifer
May 3, 20269 min read
Also available in:Deutsch · 中文

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-analytics in 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. "

#MetricLift (median)WindowSource of truth
1K-factor (30-day rolling)3.4×30 daysinvite events × pay conv
2Verified email rate2.6×rolling 14demail verification events
3Referral invite rate2.4×30 daysinvite-sent events
4Position drop (return visit)2.1×per sessionpage-view join to position
5Return-visit rate (D1/D3/D7)1.9×cohortsession events
6Source attribution at signup1.8×per signupUTM join to conversion
7Cohort retention to launch1.7×30/60/90dcohort tables
8Time-to-refer (TTR)1.6×per inviteeinvite timestamp delta
9Reward redemption rate1.5×60 daysredemption events
10Drop-off rate per step1.4×rolling 7dfunnel events
11Launch-day conversion by source1.4×D0 / D1 / D7paid 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.

typescript
// 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.

sql
-- 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.

sql
-- 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.

WeekMilestoneEffortOutput
1Ship waitlist_events schema + /api/waitlist/join6hAppend-only event log live
1Wire source capture on join (UTM params + referrer)2hSource attribution on every signup
2Implement K-factor calculator + dashboard tile4hCohort K + 60-day projection live
2Wire referral invite + reward redemption events4hInvite + redemption metrics live
3Build attribution query + channel-by-channel readout4hD0/D1/D7 conversion per source
3Build cohort retention tile (30/60/90d to launch)3hRetention curves by signup week
4Ship position-drop + return-rate composite badge5hHonest referral incentive UI
4Connect D7 paid events; lock launch-week dashboard4hLaunch-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 →