GHAN— by singular Get started

How to measure retention of referred app installs

Measuring retention of referred installs needs three things a normal analytics setup does not have: a referral tag that survives a browser download, a definition of "retained" stricter than first open, and cohorts keyed on the referrer rather than the install date. This is the schema and the formulas.

What retention means for a referred install

Three definitions have to be pinned down before any number means anything.

Referred install — an install whose source is known deterministically, because an identifier issued at click time was presented back at install time and verified. Not "arrived after a campaign was running".

Retained — recorded at least one qualifying session in the measurement window. A qualifying session is one where the application was opened and the person did something your app counts as use. Presence on disk is not retention.

Cohort — all referred installs from one referrer, in one time bucket, measured together. The denominator is fixed at the original cohort size forever.

The formula

R(N) = sessions_in_window(N) / cohort_size

where sessions_in_window(N) is the count of installs in the cohort with at least one qualifying session in the 24-hour window beginning N × 24h after the install claim timestamp — not after first open, which is a moving origin.

D1  = active in hours 24–48
D7  = active in hours 168–192
D30 = active in hours 720–744
D90 = active in hours 2160–2184

Two mistakes that make numbers incomparable:

  1. Rolling denominators. Dividing by the survivors at N−1 produces a much flatter curve that looks better and means something else. Keep the denominator fixed.
  2. Anchoring on first open. People who install and open three days later inflate D1 if you anchor there. Anchor on the claim.

GHAN's own clearing rule is a special case: in-use at 48 hours, which sits between D1 and D7 and is measured as "at least one qualifying session between hours 24 and 48".

Tagging the referral so it survives

This is the part desktop gets wrong, because the web pattern does not transfer.

A UTM parameter dies at the download. A Referer header does not reach an installer. A person can download in a browser, walk away, and run the installer twenty minutes later from their Downloads folder with no connection to the original click.

The working pattern is a token with two delivery paths:

click  →  server issues single-use token (nonce, short expiry, bound to source)
       ├→ custom protocol handler:  ghan://claim?t=<token>
       └→ handoff file written to a known local path
first run → app checks both, presents the token, signs the claim

Electron

{ "protocols": [{ "name": "GHAN", "schemes": ["ghan"] }] }
app.setAsDefaultProtocolClient('ghan')
app.on('open-url', (e, url) => claim(new URL(url).searchParams.get('t')))

Tauri

{ "plugins": { "deep-link": { "desktop": { "schemes": ["ghan"] } } } }

Native

macOS declares CFBundleURLTypes in Info.plist; Windows registers the scheme under HKEY_CLASSES_ROOT\ghan with a URL Protocol value. Most packagers generate both from the config above.

Always implement the handoff file too. Protocol handler registration fails silently under managed device policies, and the file is what keeps attribution rates from collapsing on locked-down machines.

Event schema

Six events are enough. Everything else is derivable.

EventWhenFields
referral_clickCard clickedtoken_id, issued_at, expires_at, source_app_id, campaign_id
install_claimFirst run, token foundtoken_id, claimed_at, device_hash, os_install_age_h, vm_flag, clock_skew_ms
install_claim_rejectedAny check failedtoken_id, reason_code
session_startApp openedinstall_id, started_at
session_endApp closed or idleinstall_id, ended_at, duration_s
activationCore action completedinstall_id, occurred_at, feature_key

Never store raw hardware identifiers. Salt the device hash. None of these fields is personal data, and that is deliberate — deterministic attribution does not need identity.

The cohort query

WITH cohort AS (
  SELECT c.install_id, c.source_app_id, c.claimed_at
  FROM install_claim c
  LEFT JOIN install_claim_rejected r USING (token_id)
  WHERE r.token_id IS NULL
    AND c.claimed_at >= :from AND c.claimed_at < :to
),
active AS (
  SELECT DISTINCT co.install_id, co.source_app_id,
         FLOOR(EXTRACT(EPOCH FROM (s.started_at - co.claimed_at)) / 86400)::int AS day_n
  FROM cohort co
  JOIN session_start s ON s.install_id = co.install_id
  WHERE s.started_at >= co.claimed_at
)
SELECT co.source_app_id,
       COUNT(DISTINCT co.install_id)                                        AS cohort_size,
       COUNT(DISTINCT a.install_id) FILTER (WHERE a.day_n = 1)              AS d1,
       COUNT(DISTINCT a.install_id) FILTER (WHERE a.day_n = 7)              AS d7,
       COUNT(DISTINCT a.install_id) FILTER (WHERE a.day_n = 30)             AS d30,
       ROUND(100.0 * COUNT(DISTINCT a.install_id) FILTER (WHERE a.day_n = 1)
             / NULLIF(COUNT(DISTINCT co.install_id), 0), 1)                 AS d1_pct
FROM cohort co
LEFT JOIN active a USING (install_id)
GROUP BY co.source_app_id;

Note the FILTER (WHERE day_n = N) rather than >= N: retention at day N is activity in that window, not activity at any point since.

Comparing referral against paid and organic

Only valid on identical definitions. Use the same window, the same qualifying-session rule and the same fixed denominator for every channel, then compare on the bottom row:

ReferralPaid searchOrganic
Cohort sizeinstalls with a verified tokeninstalls from tagged landing pageseverything else
SpendCPI × cleared installsad spend in the window0
Installsclaims acceptedinstalls attributedinstalls
Retained at 48hqualifying sessions h24–48same rulesame rule
Cost per retained userspend ÷ retainedspend ÷ retained

Cost per install flatters whichever channel sends the flakiest traffic. Cost per retained user is the number that corresponds to revenue, and it is frequently two to four times higher than the reported CPI.

Why a cheaper channel can lose

Channel AChannel B
Cost per installEUR 9EUR 24
48-hour retention22%81%
Cost per retained userEUR 40.91EUR 29.63

Channel A is 62% cheaper per install and 38% more expensive per user who stayed. This is the single most common error in desktop acquisition reporting, and it is why GHAN bills only on cleared installs — the rate card is already a retained price.

Those figures are an illustration of the arithmetic. They are not GHAN network data.

What GHAN reports

get_stats { app_id, period }
  → impressions, clicks, installs, cleared, retention_48h,
    floor_cents, avg_clearing_price_cents,
    fraud_rejected: { reason_code: count }

Cohort-level D1/D7/D30 by referring app pair follows once there are cohorts. There are none yet — GHAN is pre-launch, nothing has traded, and this page publishes no network retention figures because none exist. The methodology above is what they will be computed with, and the status page says what is built. When the first cohorts clear, the numbers appear here and are reproducible by any partner from get_ledger output for their own share of them.

Questions people ask about this

How do you measure retention of referred app installs?

Tag the install with the referrer at install time, not at first open, then build cohorts keyed on that referrer and measure the share still active at D1, D7, D30 and D90. The two things that break this on desktop are that a browser download loses URL parameters, so the tag must travel via a protocol handler or a handoff file, and that "still installed" is not the same as "still used", so the retained numerator has to be sessions rather than presence.

What is the formula for referred-user retention?

Retention at day N equals the number of installs in a cohort that recorded at least one qualifying session in the 24-hour window starting at N times 24 hours after the install claim, divided by the total installs in that cohort. Keep the denominator fixed at the original cohort size for every N; dividing by the surviving count at N-1 gives you a different and much flatter-looking metric that is not comparable to anyone else's.

How do you track referred installs in desktop apps?

With a token, not a URL parameter. Issue a single-use identifier at click time, carry it into the installed app through a custom protocol handler and a local handoff file as a fallback, and have the app present it on first run. UTM parameters die at the download, and referrer headers do not survive an installer.

What is the difference between install retention and in-use retention?

Install retention asks whether the software is still present on the machine. In-use retention asks whether a person opened it and did something. The gap between them is large - an app can sit installed and unopened for months - and only the second correlates with revenue. GHAN clears payment on in-use retention at 48 hours for exactly this reason.

How do you compare referral retention with paid acquisition?

Only on identical definitions, which almost nobody does by default. Use the same retention window, the same session definition and the same fixed denominator for both, then compare cost per retained user rather than cost per install. Most desktop teams running this comparison for the first time find their paid cost per retained user is two to four times their reported cost per install, which frequently inverts the channel ranking.

Why is first open not enough?

Because first open measures whether the installer worked, not whether the software was wanted. A person who opens an app once and never returns is indistinguishable at first open from your best customer. On a network that pays per install, first open is also trivially cheap to manufacture, which is why GHAN clears on 48-hour usage instead.

Machine-readable versions of this page: markdown · llms.txt · llms-full.txt · OpenAPI · AI catalog