How to stop spam on public signup forms
How to stop spam on public signup forms with honeypots, domain gates, rate limits, MX checks, and optional Turnstile.
To stop spam on a public signup form, layer cheap defences: a hidden honeypot field, an exact-match domain allow-list, per-IP rate limits, an MX check on the email domain, and an optional Turnstile challenge. No single control is enough — together they drop the overwhelming majority of automated junk before it reaches your list.
The layers
- Honeypot — a hidden
hpfield humans leave empty; bots fill it and get dropped. - Domain allow-list — only your exact hostnames may submit, so scripts posting from elsewhere are rejected.
- Rate limits — cap submissions per IP to blunt bursts.
- MX check — reject addresses whose domain cannot receive mail.
- Turnstile — an optional challenge for the hard cases, without CAPTCHA friction for most users.
Which layer stops what
- Filled honeypot — silently discarded before it ever looks like a real signup.
- Wrong origin or referer — rejected as
DOMAIN_NOT_ALLOWED. - Bursts from the same client or campaign — throttled as
RATE_LIMITED. - Failed challenge — rejected as
BOT_CHECK_FAILEDwhen Turnstile is required. - Undeliverable domain — rejected as
EMAIL_UNDELIVERABLEwhen the MX check can definitively prove no records.
The honeypot in practice
Keep the honeypot visually hidden and out of the tab order. A filled hp is silently discarded.
<form action="https://simple-signups.com/api/subscribe" method="POST"> <input type="hidden" name="campaignId" value="pub_your_campaign_id" /> <input type="email" name="email" required placeholder="you@example.com" /> <input type="text" name="hp" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true" /> <button type="submit">Subscribe</button></form>When to add Turnstile
Start with the cheaper layers first: honeypot, domain gate, rate limits, and MX checks. Add Turnstile when you see abuse that still gets through, or when the form lives on a page that attracts aggressive automated traffic. It adds friction, so it is best used where the earlier layers are no longer enough.
Defaults, not homework
Simple Signups runs these layers on every submission so they are not left as future TODOs. The exact behaviour and error codes are documented under anti-abuse.