Guides

How to stop people creating multiple accounts on your free tier

October 29, 2026 · 4 min read

If your free tier is generous, someone will open ten accounts to get ten times the free tier. You can't make that impossible, but you can make it unprofitable, and you can do it without treating every real user like a suspect.

This is the playbook we'd follow, in the order we'd build it.

First, decide what "one account" means

Before writing code, write down your rule. Usually it's something like "one free tier per person", with sensible exceptions: a person can have a work and a personal account, and a team can have several members on one network. That rule decides which signals you use as hard limits and which as soft hints.

A shared office IP is not a person. A single browser that has created twelve accounts this week usually is.

Layer 1: Normalize emails

Most multi-accounting starts with email tricks, so start there.

  • Lowercase the whole address.
  • For gmail.com and googlemail.com: remove dots from the username, drop everything after +, and map googlemail.com to gmail.com.
  • For other providers, optionally drop +tags too. Some company mail servers treat + as a literal character, so use normalization for matching, never for rewriting the address you send to.
function normalizeEmail(raw) {
  const email = raw.trim().toLowerCase();
  const at = email.lastIndexOf("@");
  let local = email.slice(0, at);
  let domain = email.slice(at + 1);
  if (domain === "gmail.com" || domain === "googlemail.com") {
    domain = "gmail.com";
    local = local.split("+")[0].replace(/\./g, "");
  } else {
    local = local.split("+")[0];
  }
  return `${local}@${domain}`;
}

Store normalized_email with a unique index (or at least an index you check before granting credits). Paste a signup export into our email alias checker to see how many hidden duplicates you already have.

Layer 2: Block throwaway inboxes and dead domains

Use a maintained disposable-domain list (the open-source disposable-email-domains list is CC0) and check that the domain has MX records. Cache the DNS lookup and set a short timeout; if it times out, don't penalize the user.

Keep an allowlist for mainstream providers so a bad list entry never blocks Gmail or Outlook. Try any address in the disposable email checker.

Layer 3: Look at the network, carefully

  • Datacenter and hosting IPs are a moderate signal. Real people rarely sign up from AWS; scripts often do.
  • Commercial VPN ranges are weaker: privacy-conscious users use VPNs.
  • Tor exit nodes are a strong signal for a free tier with real costs.
  • Velocity: count signups per IP and per /24 subnet in the last hour. Three from one IP is odd; twenty from one subnet is a script.

Never block on a single network signal. Use them as weights.

Layer 4: Recognize the device

Farmers rotate emails and IPs cheaply. They rarely rotate the browser. A lightweight device token, a hash of stable browser properties plus a first-party id, lets you see "this device is linked to 9 different emails this month", which is the strongest multi-account signal you can get without asking users for anything.

Do this privately: hash on the client, don't set cookies, don't share identifiers across sites, and mention fraud prevention in your privacy policy.

Layer 5: Combine signals into a score

Each signal above is noisy on its own. Combine them so weak signals add up but no single weak one decides. A simple, robust method is noisy-OR:

score = 100 × (1 − Π(1 − weightᵢ / 100))

A +tag (weight 15) alone gives 15. A +tag from a datacenter IP (40) on a device already linked to five emails (90) gives about 95. That's the behavior you want.

Layer 6: Respond in proportion

This is where most homegrown systems fail: they only have "allow" and "block". You want four outcomes:

Score Decision What the user experiences
Low allow Full free tier
Medium limit Account created, a fraction of credits now, the rest after card, phone or a few days
High review Account created, verification required before any credits
Very high block Signup refused with a neutral message

The limit step is what keeps your free tier generous for real people while removing the payoff for farmers. More in credit gating.

Layer 7: Monitor first and keep a feedback loop

Turn detection on in monitor mode: log the decision, act on nothing, for a week. Review what would have been blocked. Fix thresholds. Then enforce. Keep a one-click "this was a real user" action that allowlists them. geocod.io's writeup on preventing abuse is a good reminder that rules which hurt real users get turned off.

What about phone verification and cards?

Both work, and both cost conversion. Use them as the unlock step for limited or reviewed accounts, not as a wall for everyone. Kilo Code, for example, gated free credits behind extra verification for suspicious accounts rather than forcing it on everyone. (Stytch)

Stripe Radar's free-trial abuse control is great if your trial collects a payment method, but it only sees trials that go through Stripe with a card. See what Stripe Radar covers.

Build or buy?

Layers 1 and 2 are an afternoon of work. Layers 3 to 7 are where the maintenance lives: lists that go stale, device signals, velocity windows, a review UI, and a way to measure dollars saved. BurnGuard packages all of it into one API call with recipes for common stacks. Either way, start with normalization today; it's the cheapest win you'll get.