Oxwyn Studio
All field reports
23 July 2026Security

Honeypots beat CAPTCHAs on contact forms

A hidden field and a three second timer stop most form spam without making your best leads identify traffic lights. Here is the pattern we ship.

A contact form has one job: let a person who wants to pay you tell you so. Every CAPTCHA you bolt onto it taxes that person to solve a problem they did not cause. On oxwynstudio.com we run no CAPTCHA at all, and the form stays quiet, because two invisible checks (a honeypot field and a time trap) catch the traffic a CAPTCHA would, without the friction.

The conversion cost of a CAPTCHA

A CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is a challenge inserted between a visitor and the thing they came to do. On a login page, mild annoyance. On a contact form, it sits at the single highest-intent moment of the whole site: someone has read your pricing, decided you might be worth talking to, and started typing.

That is the worst possible place to add work. Challenges fail on slow connections, behave unpredictably with browser privacy tooling, and are genuinely difficult for people with visual or motor impairments. The audio fallbacks are worse than the visual puzzles. And modern invisible variants trade the puzzle for a scoring system you cannot inspect: when it misfires, a real prospect gets blocked and you never find out. A blocked bot costs you nothing. A blocked buyer costs you the project fee, and on a small studio's contact form even a low single-digit failure rate is real money.

So the question is not "how do we stop all bots" but "how much bot traffic can we stop at zero human cost". The answer, for most marketing sites, is nearly all of it.

How a honeypot works

The overwhelming majority of form spam comes from dumb crawlers: scripts that fetch a page, find every <input>, fill each one with something plausible, and POST. They do not render CSS. They do not run your JavaScript with human timing. They fill in everything, because leaving fields blank risks tripping validation.

That greed is the vulnerability. A honeypot is an extra field that a human never sees and therefore never fills. If it arrives at the server with a value in it, a script filled it, and you can discard the submission.

Here is the client side of the pattern, taken from the contact form on this site:

{/* Honeypot: invisible to humans and to assistive tech */}
<input
  type="text"
  name="honeypot"
  value={honeypot}
  onChange={(e) => setHoneypot(e.target.value)}
  tabIndex={-1}
  autoComplete="off"
  aria-hidden="true"
  className="absolute left-[-9999px] h-0 w-0 opacity-0"
/>

Two details matter more than they look.

First, hide the field by position and opacity, not with display: none or the hidden attribute. The crude crawlers ignore CSS entirely, so any hiding method beats them, but some mid-tier bots do check display and visibility because those are cheap to read from inline styles. Off-screen positioning through a class keeps the field in the DOM, plausibly named, and filled by anything that does not do full layout.

Second, name it something a bot wants to fill. honeypot is honest and works fine in practice; fields named website, company_url or fax get filled even more eagerly because autofill-style heuristics match on the name.

A glass form with three visible input rows and a fourth, nearly invisible one: the field only bots fill in

The server side: trip it silently

The check itself is one line, and the line matters more than the field. From our server action:

// Bot trap: report success, deliver nothing.
if (data.honeypot) return { ok: true }

The submission is not saved, no email is sent via our delivery pipeline, and the bot receives a success response indistinguishable from a genuine one.

Why lie? Because an error message is an oracle. If the server responds "submission rejected", the bot operator (or the bot itself, iterating) learns that something detectable went wrong, and the fix is a for-loop away: skip hidden fields, randomise timing, retry. Silent success teaches the other side nothing. Their dashboard says delivered, their script moves on, and the spam economics stay broken in your favour. In security terms you have removed the feedback channel an attacker needs to tune against you.

This is also why the honeypot check runs before anything with a side effect: before the database insert, before the email dispatch, before any logging you might later expose.

The second layer: a time trap

Honeypots catch the greedy. A time trap catches the fast. Bots race because time is their unit cost: a spam operation POSTs a form within a few hundred milliseconds of loading the page, since every extra second per site multiplied across a million targets is real infrastructure spend. Humans, by contrast, cannot physically read a label, type a name, type an email address and compose even one sentence in under three seconds.

So we stamp the moment the visitor first interacts with the form (a focus event, not page load, so time spent reading the page does not count against them) and reject anything that completes too quickly:

// Time trap: humans do not complete the fields in under 3 seconds.
if (Date.now() - data.startedAt < 3000) {
  return { ok: false, error: "That was quick. Please take a moment and try again." }
}

Note the deliberate asymmetry with the honeypot. The time trap responds with a visible, polite error rather than silent success, because a human can trip it: a returning visitor whose browser autofills every field and who hits submit immediately is doing nothing wrong. Silent success would eat their message. A gentle "try again" costs them four seconds and loses nobody. The honeypot can stay silent precisely because a human essentially cannot trip it.

The two layers are complementary. A bot sophisticated enough to skip hidden fields is usually still racing; a bot patient enough to wait out the timer is usually still filling everything. On our own contact form, three required fields plus these two checks keep the form quiet, with no challenge shown to anyone.

Two light trails on a clock face: the bot submits in a streak, the human meanders for most of a minute

What this stack does not stop

Honesty about the ceiling, because a security measure you overtrust is worse than none.

Targeted human spam. Someone paid to paste an SEO pitch into your form by hand sees what every human sees and passes both checks. Nothing short of content filtering or manual triage stops this, and volume is usually low enough that triage is fine.

Sophisticated headless browsers. A bot driving a real browser engine (Playwright, Puppeteer and similar automation frameworks) renders your CSS, can be scripted to skip invisible fields, and can wait ten seconds before submitting. If someone targets your form specifically, this stack will not hold. It is not designed to; it is designed to remove the 95-plus percent of spam that is untargeted and cheap.

Distributed abuse. If a form triggers something expensive (account creation, outbound SMS, a costly API call), attackers have an economic reason to beat your defences, and they will. That is when reCAPTCHA-class tools, proof-of-work challenges like Turnstile, or platform rate limiting earn their friction. A contact form that sends one email is not that target. Signup forms, password resets and anything carrying financial incentive are. Match the defence to the attacker's incentive, not to your anxiety.

We also treat platform-level rate limiting (a WAF rule capping submissions per IP) as the sensible next layer if volume ever demands it. It sits in front of the application, costs humans nothing, and needs no puzzle.

The accessibility trap inside the trap

A honeypot that is invisible to sighted users but announced to screen readers is a spam filter for blind visitors. A screen reader user who is offered a mystery field, fills it in good faith, and gets silently discarded has been treated exactly like a bot, and they will never know why you ignored them.

Three attributes on the honeypot input prevent this, and all three are load-bearing:

  • aria-hidden="true" removes the field from the accessibility tree, so screen readers never announce it.
  • tabIndex={-1} takes it out of keyboard tab order, so keyboard-only users never land on it.
  • autoComplete="off" stops the browser's own autofill from helpfully stuffing a value in and flagging a real person as a bot.

Also avoid a visible label, and never mark the field required. Test with an actual screen reader, not just the attribute checklist: if VoiceOver or NVDA can reach the field, your filter is discriminating, not filtering. The full accessibility posture of the site is covered in our accessibility statement, but the honeypot is the one place where getting it wrong silently drops real enquiries.

Key takeaways

  • CAPTCHAs tax your highest-intent visitors to solve a bot problem; on a contact form the cost lands on buyers, not bots.
  • A honeypot exploits bot greed: an off-screen field humans never see, checked in one line on the server before any side effects run.
  • Respond to a tripped honeypot with silent success, never an error. An error message is an oracle the bot author can iterate against.
  • Layer a time trap on top: reject submissions completed under three seconds from first interaction, with a polite visible retry because humans can occasionally trip it.
  • Hide the honeypot from assistive tech too (aria-hidden, tabindex="-1", autocomplete="off") or your spam filter quietly filters out real people.

Frequently asked

Do honeypots still work in 2026?

Yes, because the economics of spam have not changed. Most form spam is still mass-produced by crawlers that do not render CSS or simulate human timing, since doing either at scale costs money. Honeypots and time traps remove that entire class. What they do not stop is targeted abuse, which most contact forms never receive.

Why return success to a bot instead of an error?

An error tells the bot operator their submission was detected, which invites iteration until it passes. A success response teaches them nothing: their tooling reports delivery, and they move on. You keep your detection method secret at zero cost, because the discarded submission was never going to convert either way.

Will a honeypot hurt my form's accessibility?

Only if you implement it lazily. The field must be hidden from screen readers with aria-hidden="true", removed from tab order with tabindex="-1", and protected from autofill with autocomplete="off". Miss any of these and assistive technology users can fill the field and have their genuine enquiry silently discarded.

When is a CAPTCHA actually the right call?

When the form triggers something an attacker profits from at scale: account signups, password resets, promo code redemption, anything sending SMS or spending API credit. There the attacker has an incentive to defeat honeypots, and a challenge (or bot-management layer) earns its friction. A plain contact form almost never clears that bar.

Put this to work

Want a system built like this behind your business?

Brief the studio