Theme

Start for free

Software as a service

For one-person SaaS: which launch, post or search paid.

A micro-SaaS grows on a handful of channels its maker can run alone: a launch, a few posts, a directory listing, search. Tally shows which of them brought paying customers, with the receipt behind each sale, so the next week's work goes where the money came from.

Start for free

14 days free, no card needed.

01How they sell

  • SubscriptionA monthly plan with a cheaper yearly one, often starting with a trial, so the first payment can land days after the visit that won it.
  • FreemiumA free tier that upgrades later; the upgrade is a new payment that still takes the source of the visitor's earlier visits.
  • One-offLifetime deals at launch, sold once and never renewed, which can dwarf a month of subscriptions and should be read apart.

02Payments it reads

  • StripeThe usual choice: Checkout or Payment Links with subscriptions, read with a restricted key, renewals and upgrades included.
  • Lemon SqueezyChosen as merchant of record to avoid handling sales tax; orders and subscription invoices are read, with refunds.
  • PaddleAlso a merchant of record, common once a product sells to companies in many countries; transactions, renewals and adjustments are read.
  • PolarPicked by developer-facing products; orders, subscription cycles and lost disputes are read.

What Tally can't read

  • Lifetime deals sold through a marketplace's own checkout aren't in your provider account, so they aren't read.
  • Revenue an app store collects for a companion mobile app isn't read; only the web checkout is.
  • A trial with no payment yet is not revenue: it shows as a sale only when the first charge succeeds.

03Where buyers come from

  • Product HuntA launch day brings a spike of visitors; lifetime value by first source shows whether those customers renew or leave after the launch discount.
  • Hacker NewsA Show HN can send thousands of visits in hours, mostly developers who rarely buy on the spot, which revenue per visitor makes plain.
  • AI assistantsPeople asking ChatGPT or Claude for a tool like yours arrive in their own channel, often already decided.
  • SearchComparison and alternative posts bring slower, steadier buyers, who show up as a long path to purchase.

04What Tally answers

  1. Was that Hacker News spike worth anything?

    The sales page lists each sale with its credited source; open one and its receipt shows the visits behind it, so you can count exactly which payments came from the Show HN crowd.

    Receipts
  2. Why do some subscriptions have no source?

    Coverage gives the share of revenue linked to a visitor and the reason for each unlinked sale, such as a Checkout Session your code made without the visitor's id, with the snippet that fixes it.

    Coverage
  3. Do launch customers stick around?

    Lifetime value by first source adds every renewal and refund to the source of a customer's first visit, so a launch's customers can be weighed against search's months later.

    Lifetime value
  4. Are AI assistants sending buyers yet?

    Visitors from ChatGPT, Claude, Perplexity and other assistants get a channel of their own in every breakdown, with the revenue behind them.

    Sources with revenue
  5. Can I hear about each sale as it happens?

    Sale alerts go to email, Slack, Discord, Telegram or a webhook once the sale is linked, with its credited source, so a launch day can be followed sale by sale.

    Alerts and digests

05Numbers worth watching

Coverage
Keep the share of revenue linked to a visitor high before you trust any channel's numbers; one missing metadata line can leave every sale from a checkout without a source.
Revenue by source
The money behind each source in the breakdowns, which ranks channels by what they earned rather than by visits.
Lifetime value by first source
Average lifetime revenue per customer by first source, the number that decides which channel deserves another week.
Median time to buy
The median time from first visit to first payment; with a trial in between, expect it to be at least the trial's length.

06An example

Example

Lifetime value by first source

All time: every payment since 1 Nov 2025. The period above doesn't apply.

First sourceCustomersLifetime revenueAverage valueRenewalsRepeat rate
GoogleOrganic Search325$40,560$12584552%
ChatGPTAI Assistants126$21,071$16739161%
Direct178$18,216$10237444%
NewsletterEmail94$17,362$18535769%
Hacker NewsOrganic Social84$11,741$14022755%
x.comOrganic Social115$10,190$8916133%
RedditOrganic Social84$9,016$10716040%
PerplexityAI Assistants42$6,342$15110157%
Every source1,048$134,498$1282,61651%

A customer is a visitor linked to a sale, counted under the source of their first visit. Lifetime revenue is net of refunds and chargebacks; repeat rate is the share who paid more than once (a renewal, an upgrade or another purchase). $13,167 from 426 payments not linked to a visitor is in no row.

Fig. 1Lifetime value by first source on the example site. For a micro-SaaS, compare a launch source's average with search's once both have a few renewals.

07Setting up

  1. Next.jsIn a Next.js app the tag goes in the root layout, and client-side route changes count as pageviews.
  2. StripePass the visitor's id into Checkout Sessions and subscription_data, so trials that convert and every renewal keep the source.
  3. Call tally('identify', { email }) after sign-up, so payments made later from the app's billing page link by email.

08Questions

  • How is a trial that converts credited?

    The first paid invoice is the sale, credited to the buyer's last visit that didn't come direct before it. With the visitor's id in subscription_data.metadata, it links with high confidence even when payment lands weeks after the visit.

  • Do I need all four payment providers?

    No. Connect the one you sell through; a site can connect more than one if, say, an old product runs on Stripe and a new one on Polar.

  • Will Tally count my own visits while I build?

    Not from localhost unless the tag has data-allow-localhost, and a browser that sets localStorage.datafast_ignore to true is left out entirely.