How to detect and stop fake account signups
Fake-signup defense comes in two layers — gates you build yourself and signals you call for — combined into a graduated allow, step-up, review, or block.
Contents
Most fake signups are not sophisticated. They are scripts filling a form as fast as the network allows, using throwaway email addresses, from wherever a cheap proxy happens to land. You can stop a lot of them with code you already know how to write, and you can stop more by adding a few signal checks that need data you do not have on hand. The trick is to layer the cheap defenses under the paid ones, and to be honest with yourself about what each layer actually proves.
Two kinds of defense
There are gates you build and signals you call for, and it pays to keep them straight.
Gates are logic that lives in your signup handler: a honeypot field, a per-IP velocity limit, a required email-confirmation step. They cost nothing per request, they run before you spend a cent, and no vendor can build them for you because they depend on your form, your traffic, and your tolerance for friction. Boundstone does not do rate-limiting, honeypots, or device fingerprinting. You build those.
Signals are facts about the values a user submitted that you cannot derive locally: whether an email domain is a known disposable one, whether a phone number is even a plausible number, whether an IP is a routable public address or something reserved. That is the layer Boundstone provides. Lead with the gates — they catch the laziest bots for free — and reach for signals when a gate is not enough.
Gates you build
A honeypot is a form field that humans never see and bots usually fill. Add an input, hide it with CSS (not type="hidden" — some bots skip those), and reject any submission that arrives with it populated. It is a few lines and it catches a surprising share of dumb automation.
A per-IP velocity limit counts signups from one address over a rolling window and throttles above a threshold. Keep the counter in whatever you already run — Redis, a database row, a Durable Object. This is yours to tune; a vendor cannot know that ten signups a minute is normal for your enterprise SSO and alarming for your consumer app.
Neither gate needs an API. If honeypots plus velocity limits plus email confirmation already hold your fraud to a level you can live with, stop here. You do not need to buy anything.
Signals you call for
When the gates leak, three cheap signals narrow the field. Each is a single request, and each returns a checks object listing exactly what was and was not verified — so you know the boundary of the signal, not just its verdict.
Disposable email. POST /v1/verify/email returns disposable, role_account, free_provider, and mx_found. Its checks.performed is ["syntax","mx","disposable_list","role_list"]; its checks.not_performed is ["smtp_mailbox","catch_all"]. Read that second list carefully: a positive result — valid_syntax true, mx_found true — means the syntax parsed and the domain has mail exchangers, not that the mailbox exists. Boundstone does not knock on the mailbox and does not detect catch-all domains. Spot-check a single address with the keyless /tools/email-validator before you wire anything.
Phone plausibility. POST /v1/verify/phone returns valid, line_type, and country, with checks.performed ["format","region","line_type_metadata"] and checks.not_performed ["carrier_lookup","ported_status","hlr_liveness"]. That tells you whether a number is shaped like a real number for its region — not whether it rings. Carrier lookup and HLR liveness are a paid opt-in (hlr:true, 5 credits) — never something this endpoint fakes.
IP classification. POST /v1/verify/ip returns classification, is_public, and is_bogon, with checks.performed ["format","version","range_classification"] and checks.not_performed ["geolocation","asn","hosting_datacenter","proxy_vpn_tor","reputation"]. A signup whose source IP classifies as private, reserved, or bogon is worth a second look — those addresses have no business originating public traffic. Note what is absent: Boundstone does not do geolocation, proxy/VPN/Tor detection, or IP reputation. That needs licensed data it has not shipped. This signal is plausibility, not a threat verdict.
Combine them into a graduated response
Do not treat any one signal as a yes-or-no verdict. Score them, and map the score to a graduated action:
- Allow — gates pass, all signals clean.
- Step up — one soft signal (a free-provider address, a role account): require email confirmation or a second factor.
- Review — two or more signals, or a phone that will not parse: queue for a human or a slow lane.
- Block — honeypot filled, or a bogon source IP paired with a disposable email: refuse.
A single disposable address is a nudge, not a conviction. Reserve "block" for the combinations that leave no honest doubt.
Wiring it together
The gates run first and cost nothing; the API calls run only for submissions that clear them. Keep the bs_live_ key server-side — it is a secret, never a value in a browser or a form field.
// server-side signup handler
export async function handleSignup(req) {
const { email, phone, honeypot } = await req.json();
const ip = req.headers.get("cf-connecting-ip");
// Gate 1: honeypot — you build this
if (honeypot) return decision("block", "honeypot");
// Gate 2: per-IP velocity — you build this too
if (await tooManyFromIp(ip)) return decision("block", "velocity");
// Signals: you call for these
const auth = { Authorization: `Bearer ${process.env.BOUNDSTONE_KEY}` };
const [e, p, i] = await Promise.all([
post("/v1/verify/email", { email }, auth),
post("/v1/verify/phone", { phone }, auth),
post("/v1/verify/ip", { ip }, auth),
]);
let score = 0;
if (e.disposable) score += 2;
if (e.role_account) score += 1;
if (!p.valid) score += 1;
if (!i.is_public || i.is_bogon) score += 2;
if (score >= 3) return decision("review", "signals");
if (score >= 1) return decision("step_up", "signals");
return decision("allow", "clean");
}
post, tooManyFromIp, and decision are all yours; Boundstone supplies the three verdicts and nothing else. For the full scoring model — weights, thresholds, and how to fold the checks arrays into one confidence read — see the multi-signal walkthrough.
The short version
Build the free gates first: a honeypot, a per-IP velocity limit, email confirmation. They catch the lazy bots and cost nothing. Add signal checks — disposable email, phone plausibility, IP classification — for what the gates miss, and read every checks.not_performed list so you know what each verdict does and does not prove. Score the signals instead of trusting any one, and map the score to allow, step up, review, or block. Boundstone gives you the signals; you build the gates. The full pattern lives in the signup-fraud use case.