Blog ·
How to stop contact form spam
Stop contact form spam with honeypots, atomic rate limits, and Cloudflare Turnstile. Why spam must never spend quota, plus a safe test plan.
- spam
- security
Contact form spam burns email budget, fills CRMs with garbage, and trains you to ignore real leads. Stop it before storage when you can; isolate it when you cannot. This is the stack that works on public form endpoints.
1. Honeypot field — always on
Bots fill every field. Humans do not see an off-screen field. Use a text input with position off-screen, tabindex=-1, autocomplete=off, and aria-hidden. Name it consistently (FormHeron expects _hp in the dashboard snippets).
<input type="text" name="_hp" tabindex="-1" autocomplete="off"
aria-hidden="true" style="position:absolute;left:-9999px" />Do not use type=hidden. Password managers treat hidden fields as fillable and will flag real people as bots. When _hp is filled, FormHeron stores the row as spam, returns a normal success to the bot, and never emails or webhooks.
2. Rate limits that are atomic
Per-IP and per-form limits only work if the counter is a single conditional write. Read-then-write counters race under bot floods. FormHeron uses an atomic upsert per time window (per IP+form, per form, per account).
3. Bring-your-own Turnstile
Cloudflare Turnstile is free. Prefer your own site key and secret so analytics stay in your Cloudflare account. Misconfigured secrets should fail closed, not open a permanent bypass. FormHeron verifies server-side with your secret; never put the secret in frontend code. Setup: docs Turnstile section.
4. Spam must not spend quota
If spam increments your monthly limit, attackers tax you even when you catch them. Store spam for review if you want; never bill it against the plan cap; never notify. That rule is non-negotiable in FormHeron.
5. Dedupe double submits
A double-clicked button is not two leads. Idempotency over a short window keeps quota and notifications honest.
6. Origin allowlist (abuse reduction only)
An allowlist stops a browser on another site from using your public key. It does not stop curl. Treat every public key as permanently compromised for read access — the key only grants submit. Real bot defence is S4–S8 style controls above, not Origin alone.
How to test without polluting production
- Honeypot: temporarily remove off-screen CSS in devtools, fill _hp, submit — expect spam status and no email
- Rate limit: fire rapid posts; expect 429 / rate_limited
- Turnstile: wrong secret should fail closed, not silently open
- Confirm spam rows do not increase monthly usage
What good failure modes look like
Invalid Turnstile secrets and 4xx from siteverify should reject the submission. Timeouts after a key was previously healthy may degrade open with a spam score bump — but a widget that has never verified successfully must not fail open forever. That split is easy to get wrong in custom code; FormHeron implements it in the submit path.
Operational signals
- Sudden spike in spam status rows — honeypot is working; investigate traffic source
- 429 rate_limited — tighten Cloudflare or wait; check you are not double-posting from the client
- turnstile_misconfigured — fix keys in form settings; submissions will reject until then
- Quota climbing without real leads — confirm spam is not being counted (it must not be)
Deeper comparison of captchas: honeypot vs reCAPTCHA. Product capabilities: features.
FormHeron is a form backend with spam controls, a lead inbox, HMAC webhooks and Slack. Free plan: 250 submissions/month. Leads stored in the EU (Amsterdam); operated from India. No raw IPs.