Stop contact form spam6 min read
How to stop Webflow form spam
Protect a Webflow form that posts to a form backend, where Webflow's own spam settings never run, with an off-screen honeypot and a human check.
Webflow's reCAPTCHA, bot blocking and spam filter only work on submissions Webflow itself receives. If your form uses a custom action to post to a form backend, those settings never see the post, so the protection has to happen at the backend. Add an off-screen honeypot in the Designer, set your domains and rate limit on the backend, and turn on its human check, which works with the normal Webflow post and no code.
What's different on Webflow
Webflow has its own spam tools: reCAPTCHA v2, a bot blocking setting that runs Cloudflare Turnstile, and a spam filter that reads each submission's content. They all live on Webflow's side, so they only act on posts that reach Webflow. The moment you set a form's Send to to a custom action, the browser posts straight to that URL. Webflow never sees the submission, so none of its checks run, however they are set.
- When Webflow handles the form, the post goes to Webflow, which runs reCAPTCHA, bot blocking and its spam filter before storing it.
- With a custom action, the browser posts the same form straight to Ikiform.
- Webflow's checks never see that post, whatever their settings.
- Ikiform runs the honeypot, scoring, rate limit and human check, then stores the post as received or spam.
Webflow's own settings, after the switch
| Webflow setting | With a custom action | What to do |
|---|---|---|
| reCAPTCHA element and validation | Not verified by anyone. Its g-recaptcha-response value arrives as an extra field | Remove the element. Turn reCAPTCHA validation off only if no form Webflow still handles needs it, because the setting covers every form on the site |
| Bot blocking | Doesn't apply to posts that go elsewhere | Leave it for any forms Webflow still handles |
| Spam filtering | Doesn't apply | Use the backend's content scoring instead |
| Form submission limits and notifications | Don't apply | The backend stores and emails instead |
The Webflow guide in the docs covers pointing the Form Block at Ikiform. The steps below add the spam protection on top.
Add the honeypot in the Designer
Webflow's quick way to hide an input is Display: none. Some bots skip fields hidden that way, so put the trap in an off-screen wrapper instead. It takes a minute and needs no paid plan:
- Inside the form, add a Div Block and give it a class such as
hp. In the Style panel set Position to Absolute, Left to-10000px, Width and Height to1px, and Overflow to Hidden. - In the Div Block's Custom attributes, add
aria-hiddenset totrue, so screen readers skip everything inside it. - Put a Label with the text Leave this empty and an Input inside the Div Block. Set the Input's Name to
_gotcha, and add the custom attributestabindex=-1andautocomplete=off. - Don't tick Required on it. A required field nobody can see blocks every honest submit.
A filled _gotcha adds 5 to Ikiform's spam score, enough to reach the threshold of 5 by itself. You can rename the field per form under Honeypot field if a template you use already has a trap with another name.
Name the fields for scoring
Webflow names each input after its label, so you get Name, Email and Field 2. Rename them in each input's settings to name, email and message. The names matter for spam scoring as well as for your data:
- A link in a name field, such as
name,first_nameorsubject, adds 3. Spammers often put their URL there, and no real person does. - An address on a disposable email domain adds 2, read from any field that holds an email address.
- Three or more links across the form add 2, and a known spam phrase adds 2.
Turn on the human check
In the Ikiform form's settings, set Spam check to Ikiform. Nothing changes in Webflow. When the form posts the normal way, Ikiform answers with a short Checking you're human page on ikiform.com that runs Cloudflare Turnstile and sends the fields on, then the visitor lands on your Redirect after submit page. No Cloudflare account, keys or custom code.
If you added the docs' script to keep Webflow's success and error blocks, it posts with fetch, and a fetch post without a token gets 403 challenge_required. This version handles that: it falls back to a normal post for the human check, and shows the wait time when a visitor hits the rate limit.
<script>
document.querySelectorAll('form[action^="https://ikiform.com/f/"]').forEach((form) => {
const wrapper = form.closest(".w-form");
const done = wrapper?.querySelector(".w-form-done");
const fail = wrapper?.querySelector(".w-form-fail");
const button = form.querySelector('[type="submit"]');
function showError(text) {
const slot = fail?.querySelector("div") ?? fail;
if (slot) slot.textContent = text;
if (fail) fail.style.display = "block";
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
const label = button?.value;
if (button) {
button.disabled = true;
button.value = button.dataset.wait || label;
}
try {
const response = await fetch(form.action, {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const result = await response.json();
if (result.ok) {
form.style.display = "none";
if (fail) fail.style.display = "none";
if (done) done.style.display = "block";
return;
}
if (result.error.code === "challenge_required") {
form.submit();
return;
}
showError(
response.headers.has("Retry-After")
? `Too many messages. Try again in ${response.headers.get("Retry-After")} seconds.`
: result.error.message,
);
} catch {
form.submit();
} finally {
if (button) {
button.disabled = false;
button.value = label;
}
}
});
});
</script>form.submit()posts the form without firing thesubmitevent, so the fallback never loops back into the script.- Paste it under Page settings > Custom code > Before </body> tag, or in the site's footer code for every form. Custom code needs a paid site plan and only runs on the published site.
- It only touches forms whose action starts with
https://ikiform.com/f/, so forms Webflow still handles are left alone.
Allowed domains and the rate limit
Under the Ikiform form's Allowed domains, add your custom domain and your staging address, such as your-site.webflow.io. Never add webflow.io on its own: every Webflow site would match, including other people's. Posts from anywhere else are stored as spam, or refused if you choose Reject.
For a contact form, set Rate limit to Strict, 2 every 10 minutes per visitor. Nobody honest writes twice in ten minutes. A signup form on a busy launch day suits Relaxed, 20 a minute per visitor better. Every form starts on Standard, 5 a minute.
spam, sends no notification email and no webhook, doesn't count toward your usage, and is deleted after 30 days. Every Monday, a workspace that blocked spam gets one summary email with the total, the busiest forms and the top reasons.Check it works
- Publish to your
webflow.iostaging domain. Custom actions only post from the published site. - Submit the form normally. With the human check on, you see the check page briefly, then your thank-you page.
- Inspect the published form and confirm the honeypot wrapper has
aria-hiddenand sits off-screen. - Post with the honeypot filled in,
_gotcha=bot. It still gets a success answer, but the submission is stored as spam and sends no email or webhook. - Open Submissions in the dashboard. The new submission is there with every field you sent.
- Nothing arrived? Switch the dashboard's status filter to Spam. Spam gets the same success answer as a real submission.
Gotchas
- Spam with Webflow's settings on. If spam keeps arriving after you turned on Webflow's tools, check the form's Send to. A custom action bypasses all of them.
- reCAPTCHA left in place. With fields defined on the Ikiform form, a leftover
g-recaptcha-responsefails with422 validation_failedas an unknown field. Remove the element. - Duplicated forms. Copying a form copies the honeypot input but not always its custom attributes. Check
tabindexandaria-hiddenon each copy. - Interactions on the wrapper. A Webflow interaction that animates every Div Block in the form can move the honeypot into view. Exclude its class.
Questions
If the form uses a custom action, Webflow's reCAPTCHA, bot blocking and spam filter never see the post. Protection has to happen where the form posts. If Webflow handles the form, try turning on bot blocking alongside the spam filter.
Add a Div Block inside the form, move it off-screen with absolute positioning, and give it aria-hidden true. Put an input named _gotcha inside it with tabindex -1 and autocomplete off, and don't make it required.
No. Ikiform doesn't verify reCAPTCHA tokens. Remove the element and set the Ikiform form's Spam check to Ikiform, which runs Cloudflare Turnstile with no keys and no code in Webflow.
Not for the honeypot or Ikiform's checks, which work with the plain Webflow form. Custom code, used to keep Webflow's success block on the page, needs a paid site plan.
Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.