A honeypot field is one of the oldest anti-spam tricks on the web, and it is a good one. You put an extra input on the form, hide it from people, and leave it empty. A human never sees it, so it stays blank. A bot reads the HTML, sees a field, and fills it in, because filling in every field is the entire strategy. Anything arriving with that field populated is a bot, and you drop it.
Ours worked perfectly. It was also, for a while, throwing away real customers.
The field was named company
That is the whole bug.
Browsers do not read your form the way you wrote it. They read the name attribute and try to match it against a list of known field types so they can offer to fill it in. The name company maps to organization, which is a standard autofill token, and organization is one of the things a browser stores in a saved address or contact card.
So the sequence went like this. Someone who had ever saved an address in their browser, which is most people, opens the contact form. The browser fills in their name and email, because that is helpful and that is what it is for. It also fills in the hidden field, because as far as it is concerned that field is asking for their employer and it happens to know their employer.
The submission arrives with the honeypot populated. The server does exactly what it was told to do and throws it in the bin.

Why nobody noticed
This is the part worth sitting with, because the bug itself is a five minute fix and the invisibility is the real problem.
A honeypot has to lie. If you return an error when the trap is tripped, you have told the bot which field is the trap, and it will drop that field and come back. So the correct behaviour is to return success. Ours returned 200 with {ok: true}, and the page showed the visitor the normal confirmation message.
Add it up from the customer's side. They filled in the form. They clicked the button. They got told it worked. They were told the truth about what they did and a lie about what happened next.
Now add it up from ours. No email. No error. No log line. No exception. Nothing in any dashboard, because from the software's point of view nothing had gone wrong. A bot had been caught, which is a success, and successes do not get written down.
The failure was completely silent on both ends simultaneously. That is a rare and nasty combination, and it is why this ran for weeks.
The two defenses that do not work
If you are about to check your own form, skip these, because I had both.
Hiding the field does not stop autofill. Ours was positioned at left: -9999px, one pixel square, zero opacity, aria-hidden, and tabIndex={-1}. All of that hides it from humans and from screen readers, which is exactly what you want. None of it hides it from the autofill engine, which works on the document, not on what is painted.
autocomplete="off" does not stop autofill either. Ours had it. Browsers deliberately ignore that attribute for address and contact fields, and they ignore it on purpose, because sites spent years using it to make their users retype their own addresses. The browser has decided it is acting for the user rather than for you, and on this one it is right.
So neither the CSS nor the attribute is load bearing. The only thing standing between a hidden field and a browser's contact card is what you called it.
The fix
Rename it to something no autofill engine has ever heard of. Ours is now liatris_hp. It is not clever and that is the point. A meaningless name cannot be matched against a known field type, so there is nothing for a browser to helpfully supply.
The second fix matters as much and is easier to skip. Log every hit.
if (liatris_hp) {
console.warn("Honeypot tripped on inquiry — discarded:",
JSON.stringify({ email, name }));
// Answer 200 so the bot believes it worked.
return NextResponse.json({ ok: true });
}
The response still lies to the bot, because it has to. But the discard is now written down somewhere a person can look. A false positive went from being invisible to being a line in a log with an email address attached to it, which means the next time this class of bug appears we find out in a day instead of never.
The test that has to fail on purpose
There is now a regression test for this, one for each form:
a browser autofilling 'company' is NOT dropped
REGRESSION: an autofilled company field is silently discarding real signups
It posts a submission with a company field populated, exactly like an autofilling browser would, and asserts that the email still goes out.
And it was verified to fail when the bug is put back. That last part is not optional. A test you have never seen fail is not evidence that anything works, it is evidence that it compiled. Reintroducing the bug and watching the assertion go red is the only thing that proves the test is attached to the behaviour you think it is attached to.
The part that generalizes
The specific bug is a browser autofill quirk and you can fix it in a minute. The shape of it is worth more.
Look for the places where your software succeeds at doing the wrong thing. Not crashes, not errors, not anything with a stack trace. Crashes are easy. They announce themselves, they get alerted on, they get fixed by whoever is on call. The expensive failures are the ones that return 200.
Our anti-spam measure worked exactly as designed. Every single component behaved correctly. The form rendered, the browser autofilled, the server checked the trap, the trap was full, the submission was discarded, the visitor got a confirmation. Not one line of that was broken. The outcome was that we lost customers and could not have known.
If you have a honeypot, go and look at what you named it. I will wait.
Gunnar
Founder, Tofte Ventures LLC