# Spam protection Form Plume stops form spam with a honeypot, time-trap tokens, rate limits, email hygiene, blocked keywords, and optional CAPTCHA. Most forms don't need to make a visitor prove they're human. Form Plume layers a few lightweight, invisible checks that catch almost all bot traffic on their own. Add a [honeypot](/docs/spam-protection/honeypot) and a [time-trap token](/docs/spam-protection/time-trap) and you're covered for the vast majority of forms. Everything else here is either automatic or for the rare form that gets targeted by an actual person. If you want an extra layer, or your form already uses one, [add CAPTCHA](#captcha-if-you-need-it). Form Plume supports Cloudflare Turnstile, hCaptcha, and Google reCAPTCHA. ## Honeypot Add a hidden input named `_gotcha` to your form: ```html ``` Real visitors never see or fill this in, it's off-screen and skipped in tab order. Bots that blindly fill every input on the page usually fill it in anyway. If `_gotcha` arrives non-empty, Form Plume responds exactly as if the submission succeeded, so the bot gets no signal that anything went wrong. The submission itself is dropped before it reaches your inbox or triggers a webhook. ## Time-trap token Bots that skip the honeypot trick still tend to submit **fast**, often under a second after the page loads. A time-trap token catches that. Fetch a token right before you render the form: ```js const res = await fetch("https://api.formplume.com/f/{public_slug}/time-trap"); const { field, token } = await res.json(); // field === "_fp_ts" ``` Then put it in a hidden field named by the response: ```html ``` The token is signed (HMAC-SHA256) and encodes when it was issued, so the server can check timing without keeping any session state. A submission comes back `validation_failed` if the token is: - Used **less than 3 seconds** after it was issued, too fast for a human to have actually read the form - **Older than an hour** (see `expires_in_seconds` in the response) - Reused, each token is good for one submission only > Rotating the signing secret on your end doesn't break anything mid-flight. > Tokens issued before the rotation keep working for a grace period. ## Rate limiting [Rate limiting](/docs/spam-protection/rate-limits) is on by default, no setup needed: | Window | Limit | | --- | --- | | Strict | 1 submission every 30 seconds | | Burst | 20 submissions every 10 minutes | Limits are keyed per form, per submitter (IP address plus user agent), so one visitor hammering refresh can't take a form down for everyone else. Going over either limit returns `429 Too Many Requests` with a `Retry-After` header telling you how long to wait. Both windows are configurable per form if the defaults don't fit your traffic. ## Email hygiene Configure this per form under **Settings → Spam**. [Email hygiene](/docs/spam-protection/email-hygiene) checks that the submitter's email field looks real: valid syntax, a domain you haven't blocked, and optionally a working mail server (MX record) and no disposable addresses. Failed checks can either **flag** the submission (it still arrives, marked for the [spam scorer](/docs/spam-protection/scoring)) or **block** it outright with `email_rejected`. Flagging is available on every plan; blocking, MX checks, and disposable-address blocking are Pro features. ## Blocked keywords [Blocked keywords](/docs/spam-protection/blocklists) let you add words or phrases under **Settings → Spam → Blocked keywords**, one per line. Any submission containing one of them anywhere in its content goes straight to the [spam folder](/docs/spam-protection/spam-inbox), no matter what the spam score says. Matching is case-insensitive and works on multi-word phrases like `backlink packages`. Quarantined submissions land in the spam folder rather than being deleted, so a false positive is always recoverable, and marking it "not spam" also trains your form's filter. ## CAPTCHA, if you need it For the small number of forms that attract targeted, human-operated spam rather than simple bots, you can add [CAPTCHA verification](/docs/spam-protection/captcha) as an extra layer. Create keys with the provider of your choice, then your page renders the widget with the **site key** and Form Plume verifies the submitted token with the encrypted **secret key** before accepting the submission. > The secret key is private. Paste it into Form Plume settings, but never put > it in your HTML, JavaScript bundle, or public repository. | Provider | Best for | Token field | Plan | | --- | --- | --- | --- | | hCaptcha | Free, challenge-based CAPTCHA with broad support | `h-captcha-response` | Free | | Cloudflare Turnstile | Privacy-friendly, low-friction checks | `cf-turnstile-response` | Pro | | Google reCAPTCHA v2 | Checkbox or invisible challenge flows | `g-recaptcha-response` | Pro | | Google reCAPTCHA v3 | Invisible, score-based risk checks | `g-recaptcha-response` | Pro | Each provider has its own setup guide with the full widget snippet, token field, and verification details: - [Cloudflare Turnstile](/docs/spam-protection/captcha/turnstile), privacy-friendly and often puzzle-free (Pro) - [hCaptcha](/docs/spam-protection/captcha/hcaptcha), the free-plan option with broad support - [Google reCAPTCHA](/docs/spam-protection/captcha/recaptcha), covering both the v2 checkbox flow and invisible v3 scoring (Pro) ## What to actually turn on Honeypot and the time-trap token together stop the overwhelming majority of automated spam for the cost of two extra fields and zero user-facing friction. Rate limiting and email hygiene run regardless of what you choose. Reach for CAPTCHA only if you're still seeing spam after that, it's usually a sign of a human spammer, not a script. Blocked keywords are a quick lever too if the spam always mentions the same things.