Spam protection
Stop bots and spam on public forms with a captcha, content rules, a review queue and daily limits, without writing code.
Every public form is protected in layers. Two are always on, and the rest are switches in the console. None of them need code: turn a captcha on, and the hosted page and the embed render it and BuildBase verifies it.
| Layer | On by default | What it stops |
|---|---|---|
| Hidden bot trap | Yes | Scripts that fill in every field they find |
| Duplicate guard | Yes | Double clicks, retry loops, the same message pasted repeatedly |
| Rate limits | Yes | Floods from one address, and a flood against one organization |
| Captcha | Per form | Bots and headless scripts |
| Content rules | Per form | People, and AI agents in a real browser, who pass the captcha |
| Held for review | Per form | Lets you see a suspicious submission before anything runs |
| Daily cap | Per form | A form taking more than you planned for |
Before you start
Choose a captcha provider once in the BuildBase console under Forms > Spam protection. Then open a form's Protect button (the shield in the forms table) to turn the captcha and the rules on for that form.
Choose a captcha provider
The provider applies to every form that has Require a captcha turned on.
| Option | Cost to you | Setup |
|---|---|---|
| Managed by BuildBase (default) | Free | None |
| Built-in check | Free | None |
| Your Cloudflare Turnstile keys | Free, unlimited checks | A site key and secret key from Cloudflare |
| Your hCaptcha keys | Free plan available; paid plans add passive mode | A site key and secret key from hCaptcha |
| Your Google reCAPTCHA keys | Free up to 10,000 checks a month per Google Cloud organization, then billed by Google | A v2 Checkbox site key and secret key |
Managed by BuildBase uses Cloudflare Turnstile run on BuildBase's own account. Most visitors see a small "Success" check and never a puzzle. On a self-hosted install without Turnstile keys, Managed uses the built-in check.
Built-in check runs a short proof-of-work calculation in the visitor's browser, typically under a second. It loads no third-party script and sets no cookie, so it suits sites with strict privacy requirements. It makes every submission cost a bot CPU time, but it does not tell a person from a headless browser on its own: pair it with content rules.
Your own keys route verification through your own provider account, so its analytics and settings are yours.
Add your own keys
- Create a widget at the provider:
- Cloudflare Turnstile: Cloudflare dashboard > Turnstile > Add widget, Managed mode.
- hCaptcha: dashboard.hcaptcha.com > Sites > New site. The secret key is under your account settings.
- Google reCAPTCHA: google.com/recaptcha/admin > Create, type v2 "I'm not a robot" Checkbox. v3 and Invisible keys do not render.
- Add the hostname the console shows in the dialog to the widget's allowed domains. That is where the hosted page and the embed run.
- Paste the site key and secret key into Forms > Spam protection and choose Check keys and save.
For Turnstile, Cloudflare confirms the secret before it is saved, so a mistyped key is reported in the dialog. hCaptcha and Google only confirm a secret against a real visitor's token, so after saving keys for either, open the hosted form and submit it once.
Note
The secret key is write-only. It is encrypted at rest and never sent back to the console or any browser. The dialog shows that a key is saved and its last four characters. To rotate it, paste the new one and save. Switching to Managed or Built-in deletes the stored keys.
If a stored key later becomes unreadable, or a provider rejects it, forms fall back to Managed rather than accepting submissions without a check.
Turn protection on for a form
The Protect dialog holds every per-form setting. The shield icon is filled when any of them is on.
| Setting | Default | Effect |
|---|---|---|
| Require a captcha | Off | Submissions must carry a verified token from your provider |
| Most links allowed | No limit | Counts links across all answers. Fields of type link are not counted |
| Blocked words and phrases | None | Whole words or phrases, any capitalization, up to 200 entries |
| When a submission breaks a rule | Hold | Hold for review or Reject it |
| Block throwaway email addresses | Off | Mailinator, Guerrilla Mail, 10 Minute Mail and similar |
| Also block these domains | None | Your own list, matched with subdomains |
| Most submissions per day | No limit | Counted over a rolling 24 hours |
Warning
Leave Require a captcha off for a form that your own backend or your own markup posts to, unless that code sends a token. See Custom forms.
The rules are never sent to the public form page, so a spammer cannot read which words or limits to avoid.
Held for review
With Hold for review, a submission that breaks a link or word rule is
saved but fires nothing: no form.submitted webhook, no workflow, no email.
The visitor sees the normal success message.
On the form's page in the console, held submissions show a Held for review
badge. Hover it for the reasons, for example 3 links (limit 1) or
Contains blocked word "casino". Then:
- Approve releases it.
form.submittedand the record-created event fire as if it had just arrived, so workflows run normally. - Delete discards it. Nothing it would have triggered runs.
Email domain rules never hold: the visitor is asked for a different address, since a submission from a throwaway address cannot be followed up.
Test before going live
Each provider publishes test keys that run the real widget and the real verification, and always pass. Save them in Forms > Spam protection to try the whole flow on a draft form, then replace them with your own keys.
| Provider | Test site key | Test secret key |
|---|---|---|
| Turnstile | 1x00000000000000000000AA | 1x0000000000000000000000000000000AA |
| hCaptcha | 10000000-ffff-ffff-ffff-000000000001 | 0x0000000000000000000000000000000000000000 |
| Google reCAPTCHA | 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI | 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe |
Test widgets show a "for testing only" line, so never leave them on a live form: with test keys, every visitor passes.
After saving your real keys:
- Open the form's hosted page from the console and complete the captcha.
- Submit it. The submission should appear on the form's page.
- If the visitor sees "Could not verify the captcha", the provider refused your secret key or hostname. Check both in the provider's dashboard.
| Situation | Status | Message |
|---|---|---|
| Captcha missing or failed | 400 | Captcha verification failed. Please try again. |
| Captcha provider unreachable | 503 | Could not verify the captcha. Please try again. |
| Blocked or throwaway email domain | 400 | Please use a different email address. |
| Content rule, set to Reject it | 400 | Your submission could not be accepted. |
| Content rule, set to Hold for review | 200 | The normal success message |
| Any rate limit, daily cap or plan limit | 429 | Too many submissions. Please try again later. |
| Hidden bot trap filled, or a duplicate | 200 | The normal success message; nothing is stored |
The hosted page shows each message in a toast and resets the captcha so the visitor can try again. The quiet answers are deliberate: telling a bot which check caught it tells it what to change.
Self-hosting
Nothing is required: Managed uses the built-in check until the platform has Turnstile keys. To run Managed on your own Cloudflare account, set both:
| Variable | Purpose |
|---|---|
FORMS_TURNSTILE_SITE_KEY | Public site key handed to the hosted page |
FORMS_TURNSTILE_SECRET_KEY | Verifies tokens on submit. Keep it secret |
FORMS_CHALLENGE_SECRET | Optional. Signs the built-in check; derived from JWT_PASS when empty |
Next Steps
- Share and embed - put a protected form on any site.
- Custom forms - send a captcha token from your own UI.
- Workflows - act on
form.submitted.