Security & Maintenance

Contact Form Spam: Turnstile, Honeypots, and Why reCAPTCHA Is No Longer the Default

Wall of stacked brown envelopes filling the frame
Who: Developer Effort: 1-3 hours for a single PHP or CMS form Cost: free — Turnstile and honeypots cost nothing
Stop contact form spam with layers, not puzzles: an invisible honeypot field first, a managed Turnstile widget second, and server-side token verification always. reCAPTCHA still works but now carries friction, a Google cookie, and quota quirks that make it the weaker default in 2026. First action: add a honeypot field today — it costs ten minutes and silently drops most bot submissions.

Every contact form on the internet is a tiny public mail relay, and the bots found yours a long time ago. The result is a familiar morning: three real inquiries a month buried under fifty pitches for SEO services, crypto, and "your website has errors."

The instinctive fix — slapping a puzzle CAPTCHA on the form — solves the bot problem by taxing every real visitor instead. This guide covers the layered approach that keeps the form invisible to humans and hostile to bots: honeypot, managed challenge, and a server-side check that treats the widget's token as a claim to verify, not a fact to trust. The same pattern runs this site's own contact form.

Why forms get spammed — and why puzzles are a poor answer

Form spam is industrial: bots crawl the web for <form> tags, fill every field, and submit. They are not attacking you specifically — you are a row in a scrape list. That matters for defense, because most submissions come from the dumbest possible bots, and dumb bots are cheap to defeat.

The trap is that the historically dominant fix, Google's reCAPTCHA, addresses a verification problem — "is this a human?" — when most sites actually have a triage problem: "is this submission worth delivering?" Verification tools force every visitor to prove humanity; triage tools quietly discard the garbage. Keep both ideas in the toolbox, but lead with triage.

The defenses, compared

Defense How it works Visitor friction Cost
Honeypot field Hidden input (website, company_url) that bots fill and humans never see None — invisible Free
Time trap Reject submissions faster than a human can type None Free
Turnstile (managed) Cloudflare's non-interactive challenge: proof-of-work, browser signals, no puzzle Near zero; checkbox only for suspicious visitors Free
reCAPTCHA v2 Checkbox + image puzzles for flagged users Highest — real puzzles Free tier ~1M assessments/mo
reCAPTCHA v3 Score 0.0-1.0 from behavioral signals; you set the threshold Invisible, but silently fails open when over quota Free tier ~1M/mo; Enterprise from 10k free
Content filter (Akismet etc.) Scores message text for spam patterns None Paid tiers for commercial use
Rate limiting Cap submissions per IP/session None until abused Free

Prices and quotas are as of 2026 per the vendors' own documentation — reCAPTCHA's FAQ and Turnstile's docs.

Why reCAPTCHA stopped being the default

Nothing about reCAPTCHA is broken — it remains the largest bot-signal network on the web. But three shifts moved it out of the default slot for small sites:

  • Friction moved wrong. v2 still shows image puzzles to exactly the visitors you most want to hear from — the hesitant ones on mobile. Every puzzle is a measurable abandonment point on a form that exists to generate inquiries.
  • Privacy posture got heavier. reCAPTCHA sets the _GRECAPTCHA cookie and sends behavioral signals to Google for risk analysis — which means consent-banner and privacy-policy obligations in EU-facing setups. Turnstile's documentation states it processes only the data needed for the security function and does not touch form contents — a materially lighter disclosure burden.
  • Quota behavior is sharp-edged. Per Google's own FAQ, v3 keys that exceed the free monthly quota fail open — returning a passing score with an error string, silently, for the rest of the month. The worst failure mode is the one you cannot see.

The honest case for reCAPTCHA: if your site already lives in Google's ecosystem, its signal depth is unmatched, and v3's invisibility is genuinely good UX — when under quota. Choose it knowingly, not by default.

The layered setup that works

This is the arrangement this site uses, and it is the right shape for any PHP or CMS form:

  1. Honeypot first. Add a real-looking field — name="website" — hidden with CSS (never type="hidden"; smarter bots skip those). When it arrives filled, return a fake success so the bot moves on. Never return an error that teaches it to retry.
  2. Turnstile managed widget on the form. The widget types matter: Managed shows a checkbox only to suspicious visitors, Non-interactive never does. Embed the script, put the widget div inside the form, done client-side.
  3. Verify server-side, always. The widget emits a cf-turnstile-response token. Your endpoint POSTs it to https://challenges.cloudflare.com/turnstile/v0/siteverify with your secret:
$payload = http_build_query([
    'secret'   => $secret,              // from config outside the web root
    'response' => $_POST['cf-turnstile-response'] ?? '',
    'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
]);
$ctx = stream_context_create(['http' => [
    'method' => 'POST',
    'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
    'content' => $payload,
    'timeout' => 10,
]]);
$verify = json_decode(@file_get_contents(
    'https://challenges.cloudflare.com/turnstile/v0/siteverify', false, $ctx), true);
  1. Check the full response, not just success. Also compare action against your form name and hostname against your domain — a valid token minted for another site is still valid. This site's endpoint does exactly that before calling mail().
  2. Keep the secret out of the web root. Ours lives in a config.php one directory up, written by CI — never in the repository and never in dist/.
  3. Rate-limit and log sensibly. Cap submissions per IP, and log a hash of the IP, not the raw address — you get abuse tracking without building a store of personal data.
  4. Test the failure path. Submit with no token and confirm a 403-style rejection, not a 500. A form that errors loudly on missing verification is telling you it would fail open under stress.

What still gets through

Layers stop bots; they do not stop people. Human spam farms and determined manual pitches will always produce a trickle — typically a few messages a month on a small site. The answer there is boring: a content filter if volume justifies one, a faster delete key if it doesn't. What you should never do in response is raise the challenge difficulty — that punishes your real prospects for someone else's abuse.

Also worth remembering: spam defenses sit on top of basic site hygiene, not instead of it. If the CMS underneath is out of support, a perfect form is a locked door on an open house — our end-of-life check guide covers that side. Browse the full guides index for the rest of the maintenance stack.

— Editorial team. Facts current as of 2026; we revise guides when the ground shifts.

Frequently Asked Questions

Do I need Turnstile if I already have a honeypot?

For a low-traffic site, a honeypot alone removes most automated submissions — try it first and watch the log. Add Turnstile when spam resumes, because better bots render JavaScript and skip hidden fields.

Is Cloudflare Turnstile really free?

Yes — Turnstile is free and does not require putting your site on Cloudflare's CDN, per its own documentation. You create a site key and secret in a free Cloudflare account and verify tokens server-side.

What about spam sent by humans, not bots?

No challenge stops a person pasting a sales pitch. Rate-limit submissions per IP, and if volume justifies it, route messages through a content filter like Akismet. Expect a trickle of manual spam regardless — it is the cost of an open form.