Blog
Contact form spam: honeypots, rate limits & Turnstile
· 3 min read
Publish a form, wait a few days, and the bots find it. Understanding how they operate makes the defenses obvious - and explains why the right answer is layers, not one nuclear CAPTCHA.
Know your enemy: three kinds of form spam
- Dumb crawlers - scripts that find every
<form>on the web and blast POSTs at the action URL. They fill every field they see. This is the vast majority of volume. - Targeted floods - someone (or someone's botnet) hammering your specific endpoint, often just because it's there.
- Human spam farms - actual people paid to fill forms. Rare for contact forms; no automated defense fully stops them, and that's okay - they're a rounding error.
Each layer below maps to one of these.
Layer 1: the honeypot (stops the crawlers)
A honeypot is an extra input that humans never see - hidden via CSS - but that dumb bots dutifully fill because it's in the markup:
<input type="text" name="website" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px" aria-hidden="true">Server-side rule: if that field arrives non-empty, the submission is spam. Silently accept it (return 200) and discard, so the bot learns nothing. This one trick eliminates most spam with zero friction for humans - no puzzle, no click, nothing.
Details that matter: give it a tempting name (website, phone2), keep it out of the tab order, and mark it aria-hidden so screen readers skip it - accessibility-invisible, not just visually hidden.
Layer 2: rate limiting (stops the floods)
Honeypots don't help when a bot POSTs valid-looking data 500 times a minute. Per-IP and per-form rate limits do: a real human doesn't submit a contact form ten times in sixty seconds, so nothing of value is lost by throttling. The limits should live at the receiving endpoint, before storage and notifications - otherwise a flood still fills your inbox even if it's flagged.
Layer 3: Turnstile (for when pressure demands a challenge)
When a form attracts smarter bots - headless browsers that render CSS and skip your honeypot - you escalate to a challenge. Cloudflare Turnstile is the modern choice: it verifies humanity through browser signals and usually resolves invisibly, without the fire-hydrant photo quizzes. The flow is standard: a widget on your page produces a token, the receiving server verifies the token with Cloudflare, and submissions without a valid token are rejected.
Turnstile is the layer to add when needed, not preemptively - every challenge, however gentle, costs a sliver of conversion.
What this looks like when it's someone else's job
If you run your own form receiver, you're implementing all three layers - plus keeping them working forever. This is a big part of what a form backend is for. Formhook ships the honeypot, per-IP and per-form rate limits, and a per-form CORS allowlist on by default, on every tier including Free; Turnstile is a checkbox in the form settings when you want the extra layer.
Whichever route you take, the strategy is the same: invisible defenses first, human-facing challenges last, and never a CAPTCHA as the opening move.
Ship a working form in one line
EU-hosted, submissions kept forever, push notifications on every tier.
Start freeNo credit card · read the docs
Keep reading
- What is a form backend? (And when you need one)A form backend receives, stores, and forwards your website's form submissions so you don't run a server. Here's how they work and when you need one.
- Contact form testing: the pre-launch checklistA broken contact form fails silently and costs leads for weeks. The 15-minute pre-launch checklist: happy path, validation, spam, mobile, and notifications.
- Mailto links vs contact forms: which loses fewer leads?Mailto links fail silently without a mail client and expose your address to spam bots. When a mailto is fine, and when a contact form pays for itself.