Features

High-value, still cookieless.

14 jobs people actually buy analytics for. They ship today. No replay, no extra identity, no second kilobyte in the default script.

  1. 01 · Now

    Goals — the action, billed as nothing

    A signup is a signup. It is not another pageview on the invoice.

    Call cfa('signup') when someone finishes the thing you actually wanted. The dashboard shows how many same-day visitors did it, and what share that is. Named events are not billed. No extra identity, no second script.

  2. 02 · Now

    Buttons, files, outbound — named clicks, not heatmaps

    Opt-in. The 1 KB file stays 1 KB. A second helper counts the click.

    Tick “also count clicks” in site settings. That adds a second file, cf-clicks.js: outbound hosts, file extensions (pdf, zip, …), buttons, and anything with data-cfa="…". Same POST beacon, named events, not billed. Not a heatmap. The default script does not listen.

  3. 03 · Now

    Monday in the inbox, not a login habit

    The briefing you already see, mailed once a week. No extra tracking.

    Monday, once. Visitors, pageviews, top page and source from the last 7 days. Unsubscribe is a link. Switch it off in account settings. Not a daily drip.

  4. 04 · Now

    Campaigns with five UTM tags, not a query-string landfill

    utm_source, medium, campaign, content, term. Everything else is dropped in the browser.

    The script allowlists those five and POSTs them in the body, so tokens and emails never hit access logs. Inner SPA routes do not inherit the landing UTMs. The dashboard lists source, medium, campaign, content, and term separately. Messy URLs stay messy on your site — they do not become our dimension soup.

  5. 05 · Now

    Search Console, joined by page — your Google

    OAuth with your Google login. Empty on the dashboard until then — no invented clicks.

    Connect Google on the dashboard. You grant Search Console read on a property you own. The table stays empty until then — we do not invent clicks.

  6. 06 · Now

    Six tiles on the curve, click to overlay

    Unique visitors, visits, pageviews, views per visit, bounce, duration. Line, bars, or auto. Compare overlays the previous window.

    The numbers sit on the chart, not in a separate row. Click a tile to overlay. Compare draws the previous window as a dashed line, aligned point-for-point (yesterday on Today, the seven days before Last 7 days). Tiles already show the period delta. Auto uses columns for hours and short ranges, a line for longer trends, and a mix when you overlay volume with bounce or duration (counts as bars, rates as lines). Mixed units are scaled so the shapes stay readable; the tooltip keeps the real figures. Visit duration is last minus first pageview in a 30-minute visit. One-page visits are 0 seconds. No heartbeat, no extra script.

  7. 07 · Now

    Notes on the curve — yours, or a campaign that just showed up

    You write a launch. The first day a UTM campaign arrives, we stamp it. Volume stays on the chart.

    Vertical line, tooltip, list. Type a note, or we add “Campaign: …” the first time that utm_campaign (or source / medium) is seen. Day-over-day traffic % is not a note — the tiles already show that. The Monday briefing is separate and is not mailed from these stamps.

  8. 08 · Now

    Many sites. Viewers. A public code.

    Starter: 5 sites, 3 viewers. Growth: 10 sites, 10 viewers. Public link plus a snippet for the website.

    Invite colleagues at /app/team — they sign in and see dashboards, not settings. A public /s/… link needs no account. Turn it on and you get a share code (the site public id) plus a promote link and an embed iframe. Two or more sites open on an all-sites board: each tile is that domain’s visitors, not a combined graph. Viewers are included in the plan, not a €12 SKU.

  9. 09 · Now

    Locations: countries, regions, cities — still no café

    Natural Earth outlines. Click a country to zoom. Tabs for regions and cities from the same edge headers.

    Natural Earth 110m outlines on the dashboard. Click a country to filter and zoom. Regions and cities are copied from Vercel or Cloudflare request headers (US-CA, not a MaxMind city database). No IP, no lat/long, no postal code, no map tiles. Tests fail an empty map.

  10. 10 · Now

    Live — last page, five minutes

    Who is on which path right now, without a heartbeat.

    Distinct visitor hashes in the last five minutes. The path is their last pageview. A tab left open does not ping. No extra script. The pill at the top of the dashboard jumps to the list.

  11. 11 · Now

    Read API — the dashboard as JSON

    A hashed bearer. The same visitors, pages, sources, and live you already see.

    Starter and Growth. Mint cfa_live_… on Account. We store a hash, show the secret once. GET /api/v1/sites and overview, pages, sources, live. Growth adds funnels. No visitor hashes. No raw pageview dump. Hobby stays the dashboard. MCP is the next card — same token.

  12. 12 · Now

    MCP — the dashboard in Claude and Cursor

    Ask after a deploy. OAuth 2.1 in Claude and Cursor, or the hashed bearer. No visitor hashes.

    Starter and Growth. POST /mcp is Streamable HTTP JSON-RPC. Tools: list_sites, get_overview, get_pages, get_sources, get_live, get_funnels. Claude and Cursor sign in on this host — that is OAuth 2.1, not a Google access token. curl still uses cfa_live_… from Account. The model sees aggregates, not hashes. Recipe: /blog/cookieless-analytics-mcp.

  13. 13 · Now

    Funnels — linear, same day, Growth

    Drop-off between pages and events. A hash cannot cross midnight.

    Two to eight steps: a path or a cfa() event. Visitors who hit step 1, then 2, on the same UTC day. The salt is deleted at midnight, so we do not pretend a funnel spans days. No extra script. No session replay. Hobby and Starter keep a single goal.

  14. 14 · Now

    Set up this site — from pages you already have

    A checkout funnel, speed pages, and goals from events that already fire. You confirm. Nothing invented.

    The dashboard can read your homepage menu and the paths that already have pageviews. On Growth it can save a same-day funnel (landing → product → checkout) if those URLs exist. Goals still need a cfa() event that already fired. Grok names the plan when xAI is connected; without it we still pick from the page list. Owner click, not a silent rewrite. Grok naming shares the period-briefing daily cap.

For engineers

The moat is how we collect and how we forget. Full recipe on /methodology.

How do you count a unique visitor without cookies?
HMAC-SHA256 of site, IP and user-agent, keyed with a server pepper and a random salt that exists only for that UTC day. We store 16 bytes. The IP is discarded. Tomorrow the salt is deleted, so the same person is a new digest.
Why not just SHA-256(site + IP + UA + date)?
That is deterministic. Anyone with edge IP logs and the formula can remake Monday’s hash. A deleted random salt plus a pepper that is not in the database cannot. HMAC is the keyed primitive; concatenation is not. This is the visitor-side moat — recipe on /methodology.
How does the beacon leave the browser?
POST JSON via navigator.sendBeacon. The payload is not in the URL, so CDN access logs do not see ?email= or ?token=. The shipped script does not GET. The collect URL still accepts a stripped GET if something else calls it.
Which query parameters do you keep?
utm_source, utm_medium, utm_campaign, utm_content, utm_term. The script allowlists them. The server allowlists them again. Everything else is dropped before it leaves the tab.
Do you store the full referrer?
No. Origin + path, no query. Source labels (Google, Hacker News, Direct) come from the hostname. Tokens in a referrer query never land on the row.
Does it count client-side / SPA navigations?
Yes. pushState, replaceState, popstate. The same path is not counted twice. Campaign tags stick to the first pageview in the tab — inner routes do not inherit them. After the landing, the referrer we send is the previous path on your host, so those hits are Direct, not another HN session.
How small is the file with all of that?
Under 1 KB gzipped, measured from /cf.js on this build. HMAC is server-side. POST, the allowlist, SPA hooks, and a prerender skip are in the script. Auto click-listeners are not.
Do prefetch and bots inflate the count?
Prerender/prefetch is dropped in the script (document.prerendering) and again if Sec-Purpose says prefetch. Library and crawler user-agents are dropped. Hostname must belong to the site, except on the public demo. Trailing slashes are folded so /pricing and /pricing/ are one path.
Do you store city-level location?
The edge already classified the request. We copy country, ISO 3166-2 region (as US-CA so California is not Canada), and a city name. We do not store IP, latitude, longitude, or postal code, and we do not run MaxMind or any other geo-IP database. If the host sent nothing, the fields are empty.
What stops someone flooding /api/collect?
Public ingest is not unforgeable. 60 beacons per hashed IP per minute — one flood, not a busy site. The counter lives in Postgres, so every Frankfurt isolate shares it. Over that IP quota we return 204 and write nothing. A popular site is not dropped. Raw IP is not stored on that row.