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

  1. Honeypot: temporarily remove off-screen CSS in devtools, fill _hp, submit — expect spam status and no email
  2. Rate limit: fire rapid posts; expect 429 / rate_limited
  3. Turnstile: wrong secret should fail closed, not silently open
  4. 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.