DEV Community

WEBVOGUE Technologies
WEBVOGUE Technologies

Posted on

How I verify a startup's traffic without trusting the founder

 Every week my timeline has a "we just hit 10k visitors 🚀" post with a cropped screenshot. Maybe it's true. There's no way to tell.

So I built Pingsold: a startup directory ranked by traffic read straight from each startup's own analytics. Nobody types in a number, including me. Here's how the verification works, and the edge cases that made it harder than "just call the GA API".

1. Read-only, or nothing

Founders connect Google Analytics, Plausible, Fathom, PostHog or DataFast.

  • Google Analytics uses OAuth with only the analytics.readonly scope (or the founder adds a service account as a Viewer). Getting Google to verify that sensitive scope took two review rounds: a data-protection section in the privacy policy and a demo video showing the scope on the consent screen.
  • The other tools use API keys. Some of those tools let you create keys that can also write or manage the account, so the connector checks what the key can do and refuses anything that can do more than read analytics. A leaked read-only key can't hurt anyone; a leaked admin key can.

We only read aggregates: visitors, pageviews, hostnames and referrer domains. Nothing about individual visitors.

2. The traffic has to be on your domain

The obvious way to cheat a traffic leaderboard: list a tiny product, but connect the analytics property of a big site you also run. So every sync pulls pageviews by hostname and checks that at least 80% of them are on the listed domain.

The details matter more than the threshold:

export const DOMAIN_MATCH_THRESHOLD = 0.8;
const PREVIEW_SUFFIXES = [".vercel.app", ".netlify.app", ".pages.dev"];

function ignorable(host: string, listingDomain: string): boolean {
  const h = host.toLowerCase().replace(/:\d+$/, "");
  // localhost, raw IPs and GA's "(not set)" aren't real traffic, so they don't count either way
  if (!h || h === "(not set)" || h === "localhost" || /^\d{1,3}(\.\d{1,3}){3}$/.test(h)) return true;
  // preview deploys aren't the product, but they aren't a different site either
  return PREVIEW_SUFFIXES.some((s) => h.endsWith(s)) && registrableDomain(h) !== listingDomain;
}

export function checkDomainMatch(listingDomain: string, hosts: { host: string; pageviews: number }[]) {
  let total = 0;
  let matched = 0;
  for (const { host, pageviews } of hosts) {
    if (!Number.isFinite(pageviews) || pageviews <= 0 || ignorable(host, listingDomain)) continue;
    total += pageviews;
    // app.example.com and blog.example.com both belong to example.com
    if (registrableDomain(host.toLowerCase().replace(/:\d+$/, "")) === listingDomain) matched += pageviews;
  }
  if (total === 0) return { ok: false, reason: "no_traffic" };
  return matched / total >= DOMAIN_MATCH_THRESHOLD ? { ok: true } : { ok: false, reason: "domain_mismatch" };
}
Enter fullscreen mode Exit fullscreen mode

Things I got wrong at first:

  • Comparing hostnames exactly. app.example.com and www.example.com are the same startup. Comparing registrable domains (eTLD+1, via the Public Suffix List) fixes that, and also handles example.co.uk correctly.
  • Counting localhost and preview deploys. A founder testing on localhost:3000 or a *.vercel.app preview would fail the check for no good reason. Those are now neutral: they count toward neither side.
  • Checking once. The check runs on every daily sync, not just at connect time, so you can't pass it once and then swap properties.

3. Credentials are encrypted, with key rotation

Stored keys and refresh tokens are encrypted with AES-256-GCM. The encryption key lives in a key ring, so it can be rotated without re-encrypting everything at once. Disconnecting deletes the credentials immediately, and for Google it also revokes the token at Google.

4. Ranking

Once a day, a scheduled job (Cloud Scheduler hitting a protected cron route) syncs every connection and stores a snapshot. Startups are ranked on two boards: top traffic (30-day visitors) and fastest growing (month over month), overall and per category. Paid options exist (faster review, a Featured slot) but they never touch the rank.

What it can't do

We're only as accurate as the analytics tool. If GA counts bot traffic, so do we. Admins can exclude a startup that's clearly gaming it, but the honest answer is that verified means "this is what your analytics says", not "every visitor was a human".

Stack

Next.js, TypeScript, Tailwind + shadcn/ui, Postgres (Neon) with Drizzle, Better Auth, Firebase App Hosting, Cloud Storage and Cloud Scheduler.


It's live at pingsold.com, free to list, and the board is tiny right now (two startups, both mine). If you run something with GA4, Plausible, Fathom, PostHog or DataFast, I'd love for you to try it, and I'd really like to hear how you'd try to break the verification.

Top comments (0)