At work, I'm rebuilding a website for an organization that serves medical laboratories. The most important page on the new site is a form where a l...
For further actions, you may consider blocking this person and/or reporting abuse
Great idea!
Thanks!
Honeypots are underrated, nice to see one used instead of a CAPTCHA. They catch the simple bots well. For the smarter ones that skip hidden fields, a second check on submit can help: look up the sender's IP and flag hosting, proxy or Tor traffic for review instead of blocking it. The fields an IP security lookup returns are listed here if useful (ipgeolocation.io/documentation/ip-security-api.html).
Thanks, I'll look into flagging instead of straight out blocking IPs. Going to be looking into the ipgeolocation.io too.
Glad it helped! One more idea since your form already asks for town and country: compare the country they type with the country their IP comes from. A mismatch isn't proof of spam (VPNs and travel happen), but combined with a hosting or proxy flag it's a good reason to send that request for manual review. The IP Location API returns the country and the security data in one call (ipgeolocation.io/documentation/ip-location-api.html). Nice work keeping the form friction-free for real users!
The mismatch-as-review-trigger framing is right — score, don't block. From the other side of the pipe (I run crawlers from datacenter IPs constantly): a signal tier like that is what keeps honest automation out of the deny-list graveyard. One production note: cache the geolocation answer per IP for a day — these APIs rate-limit, and a quote form should never fail open or closed on a third-party lookup. Friction-free for humans, scored for machines, human eyeballs on the gray zone: that's the trinity.
Good call on caching per IP for a day. It also means the form still works when the lookup is slow, which is the part I hadn’t thought through. Agree that the gray zone should go to a person, not a block.
That's the whole philosophy in one line: the form stays friction-free for humans, scored for machines, and a person owns the gray zone. Ship it 🚢
Nice write-up, and faking the success page is a good detail. Alongside the honeypot I'd also reject addresses on domains that publish a Null MX (they accept no mail) or that don't resolve at all. Those pass every shape check and still never deliver a quote reply.
The DNS tier is the cheapest validation tier there is, with one fork in the road worth drawing explicitly, because the three cases feel similar and behave very differently:
0 .). Hard-reject is safe — it's the domain owner's own signed statement.From the data side: the same three-tier split (declared / provable / inferable) is what makes a validation list survive contact with real customers — the first two tiers you can enforce, the third one you log and let humans overrule. The quote form's whole purpose is a human replying to a human; optimize the rejects to never eat a possible reply.
The Null MX check is my favorite kind of filter — it kills a whole class of fake with one standards-track lookup, no heuristics. From the bot side, the pair I'd run together is: Null MX / no-MX for the accept-no-mail domains, plus an A-record check on the website fields in the same quote form, because scraped company URLs are frequently parked domains that resolve, render a template, and forward nowhere. One deployment note from living behind flaky infrastructure: give the DNS checks a strict timeout budget (a few hundred ms, cached) and fail open on resolver failure — a spam filter that rejects mail-quote form submissions because its own resolver timed out punishes the lab manager, not the bot, and your logs will show it as a mysterious traffic dip on exactly the days your DNS was sad.
I like the idea of moving the field off the screen rather than using the hidden attribute. Did something similar because CAPTCHA seems to be more difficult to solve over time. The form is run through 9 checks, with several checks using the Neutrino API which costs us $10 per month and is worth every penny.
The 9 checks are in a Perl module (I know, I know) and you're welcome to have AI re-write the code and fix any bugs it finds. Here is the code as a text file: FormChecker.pm.txt. If you want the code that wraps around the FormChecker.pm module, I can provide as well.
Order of form validation
Awesome. Will have a look at the FormChecker code and your advice is solid stuff.
Going to look into implementing all your suggestions 🫡
This sits in front of SendGrid and if spam was to bypass these filters and since SendGrid has no chill, we'd likely be booted from their platform. This form checking code is at least 6-8 years old.
Best of luck and if you have any questions, feel free to ask.
Nice write-up, and faking the success page is a good detail. One thing to watch with this pattern: Chrome can ignore autocomplete="off" on fields its autofill recognises, and form-filling extensions don't always skip an input that is only moved off-screen. Because the handler pretends the request went through, a real lab manager whose browser filled
websitewould never find out their quote was dropped.It might be worth logging honeypot hits (just a timestamp and user agent is enough) so a human-looking one stands out, or giving the field a name autofill has no reason to touch.
Oh, I had not thought about form-filling extensions. I use one myself.
Thanks for the tips on resolving this.
Faking success is the right call — an error just teaches bots to retry. One layer that costs little: a render-to-submit time check (scripted submissions land in under a second; real lab managers don't), plus a per-IP rate limit as the backstop for anything that slips past the trap.