FormFlow
EN
Start free
Docs/ Forms/ Anti-spam

Anti-spam

Honeypot, Cloudflare Turnstile and per-IP rate limits — how FormFlow keeps bots out of your inbox.

Public form endpoints attract bots within hours. FormFlow ships three independent layers: a honeypot and Cloudflare Turnstile (both on by default, toggleable per form) and per-IP rate limits (always on, with a per-form limit).

Honeypot

On by default. The honeypot is a field that humans never see and bots love to fill in. Add it to your markup, hidden with CSS:

<input
  type="text"
  name="_gotcha"
  tabindex="-1"
  autocomplete="off"
  style="position:absolute; left:-9999px"
  aria-hidden="true"
/>

The field name defaults to _gotcha and is configurable per form — pick something that looks tempting, like website or phone2.

If the honeypot arrives non-empty, the submission is accepted with a normal 200 response — but stored with status: "spam", and:

  • no notification emails are sent,
  • no webhooks fire,
  • uploaded files are not stored.

The bot sees success and moves on; nothing reaches you. Spam submissions stay visible in the console (filtered out of the default view) so you can audit false positives, and they don’t count against your monthly quota.

Cloudflare Turnstile

Turnstile — Cloudflare’s invisible CAPTCHA alternative — is on by default for new forms. While it’s enabled, every submission must carry a valid token, so add the widget to your page (or toggle Turnstile off for that form):

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<form action="https://api.formflow.cc/v1/f/contact-form" method="POST">
  <input name="email" type="email" required />
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">Send</button>
</form>

The widget injects a cf-turnstile-response token into the post. FormFlow verifies it server-side against Cloudflare’s siteverify API; a missing or invalid token is rejected with 403 turnstile-failed. Unlike the honeypot, Turnstile failures are not silent — a legitimate user with a failed challenge should see the error and retry.

The token field is stripped before the payload is stored, so it never shows up in your submission data.

Rate limits

Every form enforces a per-IP rate limit — by default 60 submissions per IP per 10 minutes, adjustable per form. Exceeding it returns:

{
  "type": "https://formflow.cc/problems/rate-limited",
  "title": "Too many submissions",
  "status": 429,
  "detail": "Rate limit exceeded. Try again shortly."
}

Counters live at the edge (Workers KV, fixed 10-minute windows), so a flood against one form never affects your other forms or anyone else’s.

Why spam is handled silently

Returning an error to a detected bot teaches it what tripped the detection. FormFlow’s honeypot path deliberately responds with the same shape a real submission gets, so scripted senders can’t probe their way around it. The honeypot is the only layer that works this way: tripped submissions are stored with spam_reason: "honeypot" and show up in the console’s spam view. Turnstile failures and rate-limited posts are rejected outright (403 / 429) and are not stored at all — so when you’re debugging “missing” submissions, check the spam view first, then look for those errors on the client side.

Last updated: June 10, 2026