How to stop fake and spam signups with IP and email checks
Spam signups come from scripts and account farms. Check the IP and the email at the form, challenge the doubtful ones and confirm the inbox, with tested code.
Fake signups come from scripts that fill in your form and from people who farm accounts by hand. Two checks at the form stop most of them. Check the IP address for abuse on record and for servers, proxies, VPNs and Tor. Check the email for throwaway inboxes and for addresses that cannot receive mail. Block the clear cases, challenge the doubtful ones, and confirm every inbox before it gets anything of value.
#Who creates fake signups, and why?
OWASP lists bulk account creation as an automated threat of its own. The accounts are later used for "content spam, laundering cash and goods, spreading malware", and for skewing reviews and surveys (OWASP OAT-019).
Farms are people, and people pass a CAPTCHA. OWASP counts "human farms" among the ways a CAPTCHA gets solved (OWASP OAT-009). That is why the checks below look at the network and the inbox as well.
#What do you need before you start?
- The visitor's real IP on your server, read from the right header (
how to get the real client IP ). - A signup handler on your server, where the visitor cannot change the code.
- A log for each verdict and its reasons.
FFraud's IP and email checks are free and need no key (
#How do you check a signup, step by step?
- Read the visitor's IP on the server, from the header your proxy writes.
- Send the IP and the email to the two checks at the same time, with a timeout of 1.5 seconds.
- Turn the two answers into one verdict: block, challenge or allow (table below).
- On a challenge, show a CAPTCHA such as Cloudflare Turnstile, or hold the account until the email is confirmed.
- Confirm every inbox before the account gets credits, a trial or the right to post. With double opt-in, the person clicks a link in a confirmation email before they are added (Mailchimp help).
- Log the verdict with its reasons, so you can review what was stopped.
const API = "https://api.ffraud.com/public";
const RANK = { allow: 0, challenge: 1, block: 2 };
async function getJson(url, init = {}) {
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(1500) });
if (!res.ok) throw new Error(`FFraud answered ${res.status}`);
return res.json();
}
export async function checkSignup(ip, email) {
const [ipCheck, mailCheck] = await Promise.allSettled([
getJson(`${API}/ip/${encodeURIComponent(ip)}`),
getJson(`${API}/email/check`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
}),
]);
let verdict = "allow";
const reasons = [];
const raise = (v, why) => {
reasons.push(why);
if (RANK[v] > RANK[verdict]) verdict = v;
};
if (ipCheck.status === "fulfilled") {
const a = ipCheck.value;
if (a.risk === "critical" || (a.risk === "high" && a.recent_abuse)) raise("block", `IP risk ${a.risk}`);
else if (a.risk === "high" || a.risk === "medium") raise("challenge", `IP risk ${a.risk}`);
else if (a.hosting || a.proxy || a.vpn || a.tor) raise("challenge", "IP is a server, proxy, VPN or Tor exit");
} else reasons.push("IP check skipped");
if (mailCheck.status === "fulfilled") {
const m = mailCheck.value;
if (m.email_type === "invalid") raise("block", "not a usable address");
else if (m.email_type === "temp") raise("block", "throwaway inbox");
else if (m.community_blacklisted) raise("challenge", "address reported as abusive");
if (m.email_type === "alias") reasons.push("alias: one person can hold many");
} else reasons.push("email check skipped");
return { verdict, reasons };
}We ran both versions against the live API on October 23, 2026, with public test addresses, and they gave the same verdicts. Gmail allows no dash in a username, so the Gmail test address belongs to nobody (Google Gmail Help).
185.220.101.44 [email protected] -> {"verdict":"block","reasons":["IP risk critical","throwaway inbox"]}
8.8.8.8 [email protected] -> {"verdict":"challenge","reasons":["IP is a server, proxy, VPN or Tor exit"]}
4.2.2.2 [email protected] -> {"verdict":"allow","reasons":[]}
4.2.2.2 [email protected] -> {"verdict":"allow","reasons":["alias: one person can hold many"]}
4.2.2.2 not-an-email -> {"verdict":"block","reasons":["not a usable address"]}The Apple address shows why the code reads email_type. On that date the API marked it email_type: alias and also is_disposable: true. Code that blocks on is_disposable alone would turn away every user of Apple's Hide My Email.
#What does each verdict mean?
| Signal | Verdict | Why |
|---|---|---|
IP critical, or high with recent abuse | Block | Confirmed abuse, recent or from several signals |
IP medium or high | Challenge | An anonymizer, or older or weaker abuse |
| Server, proxy, VPN or Tor, low score | Challenge | A real person may use a VPN, so ask |
Email temp | Block | A throwaway inbox |
Email invalid | Block | It cannot receive mail |
| Email reported as abusive | Challenge | Reports from other sites |
Email alias | Allow and note it | A real person who may hold many |
| Everything else | Allow | No signal |
Treat an alias as a note, not a reason to refuse. Apple's Hide My Email creates "unique, random email addresses" that forward to the person's own inbox (Apple Support). Block aliases only where one person may hold only one account, such as a free trial.
#Why is an IP check not enough on its own?
Most spam sources look like ordinary connections. FFraud's open data lists 252,090 addresses confirmed as spam sources, each seen at least twice (build of October 23, 2026, 08:30 UTC). Only 11.6 percent of them carry a proxy, VPN, Tor or datacenter label:
- No network label222,853 IPs
- Proxy19,479 IPs
- Datacenter6,306 IPs
- VPN3,279 IPs
- Tor173 IPs
Source: FFraud open data (ip-fraud-database, confirmed-abusive.csv), October 23, 2026
A filter on network type alone would let 222,853 of them through. Their abuse record still catches them, since every address in the file scores 50 or more. A blank label means the network is not known, which is different from a home connection.
The email check works on its own track. A throwaway inbox is caught the same way from any network.
#How do you know it worked?
Run the five test cases above against your own handler. A Tor exit with a mailinator.com address must be blocked, and a clean address with a Gmail address must pass. Then watch two numbers each week: signups that never confirm their email, and verdicts by reason. When unconfirmed signups fall, the checks are catching what used to get through.
#What if the check is down or flags a real person?
Fail open, then confirm. With the API unreachable, our check returned allow and marked both checks as skipped (simulated outage, October 23, 2026). Treat a skipped check like a challenge: create the account, but hold it until the email is confirmed.
Expect some real people among the challenges. Shared connections, such as mobile carrier gateways, carry thousands of users, which is why FFraud caps their scores (
#Can you use the downloadable list instead?
Yes, if you want no network call at signup. FFraud publishes its list of disposable domains under the MIT license: 224,090 domains at the build of October 23, 2026, 08:30 UTC (
The list also holds alias services, such as privaterelay.appleid.com and duck.com. If you block every domain on it, you block those users too. Remove the alias domains you want to keep, or use the API, whose email_type tells the two kinds apart.
With a free API key, the dashboard has two switches, one for temporary email and one for alias email. They decide whether keyed checks treat those addresses as blocked.
#Questions people ask
How do you prevent fake signups?
Check the IP and the email at the form. Block throwaway inboxes and confirmed abuse, challenge anonymizers, and confirm every inbox before it gets anything of value. No single check is enough on its own.
Should I block disposable email addresses?
Block throwaway inboxes, which FFraud's API marks email_type: temp. Keep aliases such as Apple's Hide My Email, because they forward to a real person's inbox.
Does a CAPTCHA stop fake signups?
It slows scripts down, but OWASP lists human farms among the ways a CAPTCHA gets solved. Use it as the challenge step, next to the IP and email checks.
Accounts keep signing up and starting checkout, but they look fake. What now?
Look at their IPs and email domains in your log. Clusters from servers, from one network or from throwaway domains point to a script. Block those, and ask for a confirmed email before checkout.
Why is my inbox full of signup emails I never asked for?
Someone has entered your address into many signup forms. Sites that use double opt-in send you one confirmation and stop there, because you never click the link.
To try the checks by hand first, run an address through the
Sources
OAT-019 Account Creation OAT-009 CAPTCHA Defeat How to use Hide My Email with Sign in with Apple Create a Gmail account: choose a username About double opt-in Turnstile Email checks API reference IP intelligence API reference How we score (methodology) ip-fraud-database, confirmed-abusive.csv, build of October 23, 2026 disposable-email-domains, build of October 23, 2026