Form spam protection that does not eat real leads
The worst spam filter is the one that deletes a customer. Layer the signals, quarantine the borderline, drop almost nothing.
The real cost of spam filtering is false positives
Form spam has two failure modes, and they are not symmetric. A false negative - junk reaching your sheet - costs you a moment of deleting a row. A false positive - a real lead classified as spam and discarded - costs you a customer, and you will never know it happened. Silent deletion has no error report.
Most anti-spam advice optimizes the wrong side: stack aggressive filters until the junk stops, and accept that some humans get caught. SheetLink Forms takes the opposite position. Every screening signal short of one is advisory: suspicious submissions are quarantined for one-click review in the dashboard, never silently dropped. Approving a quarantined item delivers the row as if it had passed. Only the honeypot - a signal with an essentially zero false-positive rate - marks spam outright.
Layer 1: the honeypot
A honeypot is a field humans never see and never fill, because it is visually hidden - but naive bots auto-complete every input they find. A submission with content in the honeypot is a machine with near certainty, which is why it is the one signal allowed to mark spam outright.
Include it in every form you ship:
<form action="https://sheetlinkforms.com/f/slf_yourtoken" method="POST">
<input name="name" placeholder="Name" required>
<input name="email" type="email" placeholder="Email" required>
<input name="_slhp" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
<button type="submit">Send</button>
</form>The field name is _slhp. Note the technique: positioned off-screen rather than display:none, with tabindex="-1" so keyboard users never land in it and autocomplete="off" so browsers do not helpfully fill it for real people. If you use the sl.js embed, the honeypot is injected automatically - but shipping it in the markup keeps you covered when scripts are blocked.
Layers 2-4: rate limits, origins, and content heuristics
Behind the honeypot sit signals that are strong but not certain - which is exactly why they quarantine instead of delete:
- Rate limits, per form and per IP. A burst of fifty submissions in a minute is almost always a script - almost. A conference booth kiosk is the counterexample, and quarantine means the kiosk's leads survive review.
- Origin allowlist and CORS. Lock the form to your own domains and posts from elsewhere are refused with a
403. This one is a hard gate you opt into, not a heuristic - configure it when your form should only ever live on known sites. - Content heuristics: link stuffing, disposable email domains, and empty payloads raise suspicion. Each pattern also has legitimate exceptions (a real prospect might paste three links), which is why suspicion means quarantine.
- Timing signal: the embed records load-to-submit time. Sub-second submissions on a five-field form are not human. Slow but real typists are unaffected.
The heuristics have a per-form strictness dial - loose, normal, strict. Run normal by default; go strict during a spam wave and expect to check quarantine a bit more often; go loose when your audience trips heuristics legitimately.
Layer 5, optional: Cloudflare Turnstile
For forms under sustained attack, SheetLink Forms supports Cloudflare Turnstile - the invisible-most challenge widget that verifies humanity without picture puzzles for the overwhelming majority of visitors. Enable it per form, render the widget, and the endpoint validates the cf-turnstile-response token server-side; posts failing the challenge are refused with a 403.
It is deliberately optional. A challenge is the one layer visitors can feel, and most forms never need it - the layered passive signals plus quarantine handle ordinary spam volumes without adding any friction for real users.
The quarantine tradeoff, stated honestly
Quarantine is a philosophy with a cost, and it is fair to name it: someone has to look at the queue. A delete-on-suspicion filter is zero-maintenance; a quarantine queue asks you to glance at borderline submissions occasionally and click approve or discard. If nobody ever reviews it, borderline real leads wait there just as lost as if they were deleted - politely.
Why we still think it is the right trade:
- A reviewed false positive costs seconds; a deleted one costs a customer, invisibly and forever.
- The queue is small in practice. The honeypot already removed the bulk commodity spam outright, so quarantine holds the ambiguous residue, not the flood.
- The queue is a feedback loop. Seeing what gets held tells you whether to move the strictness dial - a deletion filter gives you no data to tune with.
If you know you will never review a queue, run strict and accept the sharper edge - but you now know the price. See the full screening flow on how it works.
Troubleshooting
- Real leads landing in quarantine: approve them (approve delivers the row), then loosen the strictness dial or review which heuristic they trip - disposable-domain flags on a student audience using temp mail is a classic.
- Spam still reaching the sheet: confirm the honeypot input is present and its name is exactly
_slhp; move the dial to strict; add Turnstile if the sender is a targeted human-driven campaign. 403on legitimate posts: the origin allowlist does not include the posting domain (staging sites are the usual culprit), or a Turnstile token is missing/expired.429responses: per-form or per-IP rate limits engaged - expected under load tests; raise the pace question in your load tool, not the dashboard.
Watch it work end to end on the live demo, or join the free beta from the waitlist.
FAQ
Is anything ever deleted without me seeing it?
Only honeypot hits are marked spam outright, because a filled hidden field is machine behavior with an essentially zero false-positive rate. Every other suspicious submission is quarantined for one-click review - approve delivers it, and nothing is silently dropped.
Do I need a CAPTCHA?
Usually not. The passive layers - honeypot, rate limits, timing signal, content heuristics - handle ordinary spam without any visitor friction. Cloudflare Turnstile is available per form for sustained or targeted attacks, and it stays invisible for most humans even then.
What does the strictness dial actually change?
It tunes how aggressively the content heuristics (link stuffing, disposable email domains, empty payloads) flag submissions for quarantine: loose, normal, or strict, per form. It does not change the honeypot, rate limits, or origin rules.
How often should I check the quarantine queue?
A glance every day or two covers most forms - the honeypot removes bulk spam before quarantine, so the queue holds only ambiguous cases. If you raise strictness during a spam wave, check more often, since a stricter dial deliberately holds more borderline items.
Can I block submissions from other websites entirely?
Yes - the per-form origin allowlist plus CORS refuses posts from any domain you have not listed (they get a 403). It is the right hard gate when your form should only exist on sites you control; leave it off for endpoints meant to accept posts from scripts or many origins. Details in the docs.
Related guides: Send an HTML form to Google Sheets without a backend ยท Capture gclid (and every other click ID) in your forms