Skip to main content
Formpaste never asks a visitor (or an agent submitting on their behalf) to solve a CAPTCHA. That’s not a missing feature - it’s the point. This guide covers how the heuristic filtering actually works and what you can do to get the most out of it, without ever adding a puzzle to your form.

Why no CAPTCHA puzzles

CAPTCHAs trade spam reduction for friction: they slow down or block real visitors, they break for screen readers and other assistive tech, and increasingly they don’t even stop bots - modern solving services defeat most of them cheaply. They also block the exact audience Formpaste is built for: an AI agent legitimately submitting a form on a human’s behalf shouldn’t have to solve a puzzle designed to detect it as non-human. Instead, every submission is scored by cheap, inline heuristics - no external API call, no per-submission cost, no visible challenge. See Spam filtering for the full mechanics; this guide is about using them well.

1. Wire up the honeypot (botcheck)

The single highest-value thing you can do is include the botcheck field and hide it properly:
Real visitors never see or fill this field in, so if it arrives with any value, the submission is quarantined. Two details matter for this to actually work:
  • Hide it with CSS (display:none), not type="hidden". Unsophisticated bots that specifically skip type="hidden" inputs will still fill in a text input that’s merely styled invisible - that’s what catches them.
  • Don’t remove it “because nothing fills it in.” An empty botcheck field submitting successfully alongside real data is exactly what should happen for a human visitor — it’s silent and free. It only matters when something else - a bot, or a browser extension autofilling every field it can find - populates it.
Every framework snippet on the Framework snippets page already has botcheck wired in; if you hand-rolled your form instead of copying one, add it.

2. Add the timing field (_ts)

An optional _ts field carries the timestamp your form was rendered. A submission that arrives implausibly fast - faster than a human could plausibly have read the form and typed a response - scores as suspicious. This is a cheap way to catch scripted submissions that skip rendering entirely and just POST a payload.
Set its value to the current timestamp at render time (e.g. Date.now() in JavaScript, or the server-render time for a static page).

3. Let the rest run automatically

Once access_key is wired, the remaining heuristics need no setup and run on every submission:
  • Link density - a message packed with multiple URLs is scored as likely spam.
  • Rate limiting - repeated submissions from the same source in a short window are throttled, independent of content.
  • Disposable / duplicate email detection - submissions from disposable-email providers, and near-identical repeat submissions, are flagged. If your use case legitimately expects disposable addresses, turn on allow_disposable_email for that form under Domain allowlist rather than fighting the default.

4. Lock down the domain allowlist

access_key is designed to sit in public page source - it isn’t a secret, and CORS on /submit is intentionally permissive so any origin can POST. The allowed domains list on each form is the actual boundary that stops someone from copying your key and routing unrelated traffic (including spam) into your inbox. An empty allowlist accepts any origin - fine while you’re setting up, but worth locking to your real domain(s) once the form is live. See Domain allowlist.

5. Treat quarantine as a review queue, not a black hole

Nothing that reaches a valid access_key is silently dropped. Anything that scores as suspicious lands in the form’s Spam tab in the dashboard instead of your inbox - check it periodically for false positives. On Free, quarantine is read-only, so a legitimate submission that was misflagged needs the visitor to resubmit once you’ve fixed whatever tripped the heuristic (usually a botcheck wiring issue or an overly strict disposable-email setting). Pro adds a one-click “not spam” rescue that re-delivers it directly.

What this doesn’t do

Heuristic scoring reduces spam volume; it doesn’t guarantee zero. A determined, well-funded attacker who renders your form in a real headless browser, waits a plausible amount of time, and sends one submission at a time from a residential IP can still get through - the same way they could defeat a CAPTCHA. The goal is raising the cost of spamming a form well above the cost of the alternative (a real inbox with no filtering at all), not building an impenetrable wall.

Spam filtering

What’s checked and how scoring works.

Quarantine

Where flagged submissions go, and how to review them.

Domain allowlist

The real security boundary behind a public access_key.

Error reference

Including rate_limited and other spam-adjacent codes.