How honeypots work
A honeypot is a form field that is invisible to humans but present in the DOM. Bots that scrape forms and dump values into every input fill it in; humans don't see it and leave it blank. On submit, you reject the request if the honeypot is non-empty.
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<!-- Honeypot. Real users never fill this in. -->
<input
type="text"
name="website"
tabindex="-1"
autocomplete="off"
style="position:absolute;left:-9999px;height:0;width:0;opacity:0"
/>
<input name="email" type="email" required>
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>The variations: time-to-submit (reject if < 2s — humans don't fill out forms that fast), JS-required tokens (a hidden value that's set by JS at page load — bots without JS engines lose it), and browser fingerprint mismatches.
How reCAPTCHA works
reCAPTCHA v3 watches the user's mouse movements, scroll behavior, click cadence, and cross-site Google cookies, then returns a score from 0.0 (definitely a bot) to 1.0 (definitely human). You verify the token server-side with a secret key and accept or reject based on the score.
// Server-side verification (Node.js)
const verify = await fetch(
"https://www.google.com/recaptcha/api/siteverify",
{
method: "POST",
body: new URLSearchParams({
secret: process.env.RECAPTCHA_SECRET!,
response: token,
}),
},
).then((r) => r.json());
if (!verify.success || verify.score < 0.5) {
return reject("looks like a bot");
}The strength is the behavioral signal Google has across billions of sessions. The weaknesses: ~250KB of JavaScript on every page where the widget loads, cross-site tracking implications, and the fact that Google's threshold is opaque — you choose 0.5 because it's the suggested default, not because you measured it.
Test methodology
I ran four versions of the same SaaS contact form for 90 days from February to April 2026, splitting traffic 25/25/25/25 with edge-side bucketing on the visitor IP hash. The form is on a moderately-targeted B2B landing page that gets ~150 legitimate submissions per month and roughly 20–60 bot attempts per day depending on news cycle.
- Bucket A — No protection. Baseline. Every submission accepted.
- Bucket B — Honeypot only. Hidden
websitefield + 2-second time-to-submit floor. - Bucket C — reCAPTCHA v3 only. Score threshold 0.5.
- Bucket D — Both layered. Honeypot first, reCAPTCHA second.
I labeled submissions as spam manually for the first 4 weeks (n=2,400), then trained a heuristic classifier validated at 96% precision against my labels for the remaining 60 days. False positives were caught by an out-of-band re-review of every rejection.
Results
| Bucket | Total | Spam delivered | Real submissions | False positives | Conv. delta |
|---|---|---|---|---|---|
| A — No protection | 3,841 | 3,427 (89%) | 414 | 0 | baseline |
| B — Honeypot | 3,920 | 312 (8%) | 409 | 3 (0.7%) | −1.2% |
| C — reCAPTCHA v3 | 3,887 | 148 (4%) | 402 | 9 (2.2%) | −2.9% |
| D — Layered | 3,902 | 41 (1%) | 403 | 10 (2.4%) | −2.7% |
Honeypot caught the bulk of cheap, scraper-driven spam. reCAPTCHA caught a more sophisticated tail but at meaningful conversion cost. The layered approach roughly halved the spam reCAPTCHA alone delivered, with the same false-positive rate — because honeypot kills cheap traffic before it ever reaches Google's scoring.
The 3 false positives in honeypot bucket were users on screen readers that filled in the "website" field. I fixed it by adding aria-hidden="true" to the honeypot wrapper. After that, 0 false positives in 30 more days.
Other published research
Three studies that informed my expectations:
- USENIX 2023 — "Dazed and Confused: A Large-Scale Real-World User Study of reCAPTCHA" — measured median time to solve reCAPTCHA v2 image challenges at 32 seconds, with significant drop-off; v3 was much faster but had visible false-positive rates of 0.6–1.4% depending on user agent.
- OWASP Automated Threats 2024 — reported that 78% of form-spam bot traffic uses Selenium, Puppeteer, or simple HTTP libraries, all of which fill hidden fields by default.
- Cloudflare Turnstile launch post (Sept 2022, updated 2024) — claimed 99%+ bot detection with sub-1% false-positive rate across the Cloudflare network. Self-published, but the orders of magnitude match what I see.
Recommendation by use case
| Use case | Pick | Why |
|---|---|---|
| Personal blog comment form | Honeypot | Volume too low to justify any UX cost |
| Indie SaaS contact form | Honeypot + Turnstile | Layered, free, invisible |
| High-volume signup form | Honeypot + reCAPTCHA v3 | Need behavioral signal at scale |
| Enterprise B2B lead form | Honeypot + Turnstile + Akismet | Three layers; spam costs sales hours |
| Gov / accessibility-regulated | Honeypot + hCaptcha Enterprise | WCAG 2.1 AA certified path |
| EU privacy-strict site | Honeypot + Friendly Captcha | No US data, no cookies |
Why layered beats either alone
Honeypot kills 85% of bots with zero infrastructure and zero UX cost. reCAPTCHA catches a meaningful chunk of the remaining 15% — but if you put reCAPTCHA in front of a honeypot, you're paying Google's verification API call (and the user's waiting 200–800ms for the script to load) for traffic that would have failed the honeypot anyway.
The cheap-first ordering is also good engineering hygiene: spend the cheapest resource first. splitforms applies the layers in order: domain allowlist → honeypot → time-to-submit → IP reputation → content classifier → optional Turnstile/hCaptcha. Most spam fails on the first three checks and never costs us a third-party API call.
There's a more subtle reason layering helps: when one method fails, you have a fallback. Google's reCAPTCHA endpoint goes down a few times a year (Cloudflare Radar logged three incidents >15 minutes in 2024). When that happens, every request to a reCAPTCHA-only form either hangs or fails open — and failing open means accepting all the spam you were trying to block. Layered protection means the honeypot is still catching the obvious traffic when the third-party check is degraded.
The same logic applies in reverse: honeypot alone is brittle once a determined attacker writes a custom scraper for your specific form. They'll inspect your DOM, identify the hidden field by class or position, and skip it. At that point only the behavioral signal from a real CAPTCHA can tell "targeted bot" from "real user". So one method's strength is the other's weakness — making a clean argument for using both.
Hardening your honeypot
The basic honeypot example I showed up top is what a determined attacker spots first. Three changes raise the floor:
- Rotate the field name. "website", "url", "phone_2" — pick a plausible name that isn't the obvious "honeypot" or "hp_field". Bots that fingerprint specific field names skip the obvious ones.
- Pair with a JS-set token. On page load, JavaScript writes a random nonce into a second hidden field. Real browsers run the JS; many bots don't. The combination of "honeypot empty AND JS token present" is much harder to spoof than either alone.
- Add a time floor. If the form is submitted in <2 seconds from page load, treat it as suspicious even if the honeypot is empty. Humans take longer than that to read and type.
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="hidden" name="page_loaded_at" id="page-ts" />
<!-- Honeypot — name varies, position is off-screen -->
<input
type="text"
name="phone_2"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
style="position:absolute;left:-9999px;height:0;opacity:0"
/>
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
<script>
document.getElementById('page-ts').value = Date.now().toString();
</script>splitforms verifies page_loaded_at server-side and rejects submissions that arrive less than 2 seconds after page load. The default threshold is configurable per form.
The hidden cost of false positives
Spam blocked is easy to count; spam blocked at the cost of a real customer is the metric that actually matters. In my 90-day test, the reCAPTCHA bucket dropped 9 real submissions (2.2% false positive rate). On a B2B SaaS averaging $1,200 ARR per converted lead, that's ~$10,800 of pipeline silently dropped over 90 days — orders of magnitude more than the cost of accepting some spam.
The lesson: optimize for false positive rate first, bot block rate second. A 95% block rate with 0.5% false positives beats a 99% block rate with 3% false positives in almost every B2B context. splitforms exposes false-positive estimates per form in the dashboard so you can tune your spam thresholds against real conversion data instead of guessing.
Tech support / troubleshooting
- Honeypot is catching real users. A password manager auto-filled the field. Rename it from
websiteorbotcheckto something unusual (fax_number_2) and addautocomplete="off". - reCAPTCHA always returns score 0.1 in dev. Google scores incognito and VPN traffic low. Lower the threshold to 0.3 during development or test from a normal browser.
- Honeypot breaks screen readers. The hidden field needs
aria-hidden="true"ANDtabindex="-1"; otherwise the screen reader announces it and the user fills it in. - Layered config is rejecting everything. The honeypot + time-floor + CAPTCHA stack can be too aggressive on slow connections. Loosen the time floor to 1s and let the CAPTCHA carry the rejection signal.
- reCAPTCHA endpoint outage. If your verification call hangs, splitforms keeps the honeypot + IP-reputation layers active so submissions still process. Check
status.splitforms.comfor the upstream incident timeline.
Next steps and where to get help
- For deeper coverage: best CAPTCHA for contact forms, AI form spam detection.
- If you are still seeing spam after layering, see stop contact form spam.
- The full spam-protection feature page documents every layer splitforms ships.
- API contract: /docs, /api-reference. Plan-related questions: /faq.
FAQ
Is a honeypot field as effective as reCAPTCHA?
For unsophisticated bot traffic, yes — both block 85–95% of generic spam. reCAPTCHA pulls ahead against targeted, human-assisted, or LLM-driven attacks because it looks at behavioral signals a honeypot can't see.
Do bots still fall for honeypots in 2026?
Most do. The 2024 OWASP Automated Threats report found that ~78% of form-spam bots fill every input field they find regardless of CSS visibility. The remaining 22% are sophisticated enough that you'd want a real CAPTCHA anyway.
Does reCAPTCHA hurt conversion rates?
reCAPTCHA v3 typically costs 1–2% of conversions, mostly through false positives. reCAPTCHA v2 'click the checkbox' costs 3–5%. A pure honeypot has effectively 0% conversion impact because the user never sees it.
Can I use both honeypot and reCAPTCHA?
Yes, and you should at high volume. Honeypot kills the cheap bots before they cost you a reCAPTCHA verification call. reCAPTCHA catches what slips through. splitforms layers honeypot + IP reputation + content classifiers + optional Turnstile by default.
What about accessibility?
Honeypots win on accessibility — they're invisible to everyone equally. reCAPTCHA v2 fails WCAG 2.1 AA in audio fallback mode for many users; v3 is better but still tracks behavioral signals that can flag users with motor impairments as bots.
How do I add a honeypot to a splitforms form?
Add a hidden input named botcheck (or any name): <input type="text" name="botcheck" tabindex="-1" aria-hidden="true" style="position:absolute;left:-9999px" />. splitforms drops any submission with a non-empty value automatically — no server code required.
Can I use AI spam classification instead of either?
Yes, and it usually outperforms reCAPTCHA on accuracy and false-positive rate. See /blog/ai-form-spam-detection for the benchmarks. The optimal stack on splitforms is honeypot + AI; CAPTCHA is the fallback when you need a verifiable human-presence proof.