Contractor form spam is not one problem. It is three arriving through one box, and the control most people install first solves only the smallest.
Pile one is scripted bots. Pile two is human beings paid to fill in forms. Pile three is agencies, SEO resellers and lead brokers soliciting you through the form you built to receive customers. A captcha is built for pile one. If you added one and the junk kept coming, nothing is broken. You just spent your effort on the smallest pile.
Generic guides also get one thing wrong for a trade. They are written for web developers and software marketers, whose inbound is patient. Yours is not. The most valuable message your form will ever receive comes from someone standing under a leak at eleven at night, and every second of friction you add to catch a bot is charged to that person first.
Sort the spam into three piles first
Open your last twenty junk submissions and label each one. The pile determines the fix.
| Pile | What it looks like | What actually stops it |
|---|---|---|
| Scripted bots | Gibberish names, mismatched geography, links in the message, submitted in under two seconds | Honeypot, timing check, invisible challenge |
| Paid humans | Plausible but vague, real typing cadence, passes every automated check | Rate limiting, content rules, making the attack uneconomic |
| Solicitations | Fluent English pitching SEO, web design, financing or leads, addressed to you rather than about a job | Content rules, removing the contact page from search results |
Most contractors have a pile three problem and have been buying pile one solutions. The tell: if the messages are grammatical, addressed to your company by name and selling something, no bot detection will help. There is no bot.
The constraint nobody writes for: your best lead is in a hurry
Cloudflare, publishing in 2021 on why it wanted to eliminate CAPTCHAs, estimated that humanity wastes about 500 years per day solving them. For most websites that is an abstract cost. For a trade it is a specific one, because your highest value inbound is also your least patient. A homeowner with water coming through a ceiling will not identify traffic lights. They will close the tab and call whoever answers.
So the rule for a contractor is stricter than the rule for everyone else: filtering belongs on the server, not in front of the customer. A developer on r/ProWordPress, laying out an anti spam stack built over years, ended on exactly that principle: "spam is the site owner's problem, not the user's, so spam prevention should not be a burden on the user." That eliminates image puzzles, math questions, multi step forms and email confirmation loops, which all work against bots and all cost you emergency calls. What follows is ordered so the controls that cost the customer nothing come first.
Filter 1: the honeypot and the clock
These two catch the majority of pile one, cost nothing, and are invisible to every real customer.
A honeypot is a field hidden from humans by CSS but present in the HTML. Scripts read the raw markup and fill everything they find. If it arrives populated, discard the submission.
Two details decide whether it works. Name it plausibly: bots skip fields called honeypot or nospam, so use a name a form would genuinely have, like middle_name, phone_ext or company_website. The last is nice judo on a contractor site, since a bot pitching you SEO is unusually eager to fill in a website field. And hide it with CSS, not type="hidden", because a hidden input is a standard field that well built scripts recognise and leave alone.
The clock is the other half. Stamp the time when the page renders, check it on the server when the form posts, and reject anything completed faster than a person could type. Three seconds is a safe floor. One developer on r/webdev does this with no third party at all, round tripping an HMAC signed timestamp: "The user will never know its even there, you collect 0 user information, and bots always get caught."
Tip
Both checks must run on the server. A validation that only runs in the browser is skipped entirely by anything posting directly to your endpoint, which is what a script does. If your form builder only offers client side validation, the check is decorative.
Filter 2: the invisible challenge, and what Google's does when you hit the cap
If you want a challenge layer, read this part carefully, because the default choice has a failure mode almost nobody knows about.
Google moved reCAPTCHA Classic onto Google Cloud. Its migration documentation sets out the timeline plainly: new Classic keys stopped being issued in Q3 2024, automated migration began in Q4 2025, and Q1 2026 was "Complete automated migration. API access locked for keys without a Google Cloud project." The part that matters operationally is further down the same page. The free tier is 10,000 assessments per month. Above that you must enable billing on a Google Cloud project. And here is the behaviour to understand before you rely on it:
Watch out
Google's documentation states that over quota SiteVerify requests fail open. They "return success:true with a score of 0.9", plus an error message noting the quota was exceeded. The stated reason is to avoid blocking valid users.
Read that as a contractor. If a spam run pushes you past 10,000 checks in a month and you have no billing account attached, your captcha does not start blocking. It starts approving everything, at a confident score, silently. The flood that exhausts the quota is the same flood the quota was meant to stop, and nothing on your site appears broken, so you find out from your inbox. CreateAssessment requests fail closed with a 429 instead, so which failure you get depends on the integration your form plugin uses.
Cloudflare Turnstile answers the same need differently. Its plan documentation lists the free plan with unlimited challenges and no monthly assessment cap to exhaust. Cloudflare's overview docs add three points that matter here: it embeds "without sending traffic through Cloudflare", so you do not move your DNS; it has an invisible widget mode; and it "does not access, store, or transmit user communications, form entries, or other page inputs."
| reCAPTCHA on Google Cloud | Cloudflare Turnstile (Free) | |
|---|---|---|
| Free tier | 10,000 assessments per month | Unlimited challenges |
| Over quota behaviour | SiteVerify fails open at score 0.9 | No assessment cap |
| Requires a cloud console project | Yes, API locked without one from Q1 2026 | No |
| Invisible mode | Yes (v3 scoring) | Yes |
| Accesses form contents | Not stated | Explicitly does not |
Practitioner reports line up. The top reply on an r/webdev thread asking for reCAPTCHA alternatives, at 60 upvotes, called Turnstile "the best state-of-art anti-bot protection currently". Another in the same thread had run both: "Even with hcaptcha we had tons of bot submissions on our forms, with turnstile we get none, even without the widget."
Most contractor forms are a generic contact box bolted onto a template, which is why they collect spam as efficiently as they collect customers. We build the lead generation system around a dedicated conversion page and a qualifying form that arrives with the answers already attached, so a real job is distinguishable from noise before anyone picks up the phone.
Filter 3: three content rules that only work for a trade
This is where most of your remaining spam dies, and it is the section a generic guide cannot write. Every business gets told to filter on content, and for most of them it is a blunt instrument, because a legitimate inquiry to a software company or a design agency can contain almost anything. A real inquiry to a trade has a narrow, predictable shape, and that predictability is an asset nobody is telling you to spend.
Rule one: reject any message containing a URL. Think about who sends you links. A homeowner whose furnace is making a noise describes the noise. They do not paste a website. An SEO reseller, a lead broker, a financing pitch and a link building bot all need you to click something, so a link is close to mandatory for them and close to unheard of for your customers. A developer on r/webdev put it bluntly: "If your contact form does not need links, just reject anything that looks like a URL, including the usual hxxp style obfuscation, and a lot of them will give up fast."
For a general contact form this is too aggressive, which is why you rarely see it recommended. For a residential trade it is close to a free win. If you also serve commercial clients who send plan links, apply it to the quote form only.
Rule two: require a service address, and validate it against your service area. Real work happens at a property. Spam has no address, because there is no job. A required postal code or street address, checked server side against the areas you actually serve, removes the entire category of submissions that are not about a location. It also catches the geographically absurd ones contractors keep reporting: the r/Contractor operator who restarted paused Google Ads and immediately got quote requests from "city/towns not even in my area or state." If your leads arrive from the wrong places for reasons other than bots, the channel by channel diagnosis is in why all your leads come from outside your service area.
Rule three: watch for procurement vocabulary. This is the newest pattern and the one that will matter most over the next year. A specialty residential contractor posted to r/Contractor that bid requests through their site were "getting better and better at sounding like a legitimate person", and named the tells: vagueness about the project and the site location, plus "verbiage that a normal person wouldn't use, like 'seeking a reputable company to handle the installation,' or 'scope of work to include, but not limited to.'"
That is what an AI written inquiry sounds like. A real homeowner writes "my AC is blowing warm and it's 30 degrees". They do not write "scope of work to include, but not limited to." A short list of formal procurement phrases, scored rather than blocked outright, catches a category that passes every technical check because a language model wrote it and a human pressed submit.
Filter 4: score, do not block
Score and quarantine. Do not delete. A developer on r/ProWordPress described the working version: assign points for each suspicious signal, then reject above a threshold. A link might be decisive by itself, while a .ru address, non English characters on an English only site, or an all caps subject line are each worth one point and conclusive together.
Two rules make this safe for a contractor. Route rejects to a folder, not the bin, and review it for a week: a false positive on a $14,000 job costs more than a hundred spam messages do. And fail quietly, showing a normal success page while dropping the submission server side. Telling a spammer they were blocked tells them what to change. As one r/webdev commenter put it, "silently ignore the contact form if it's from a given IP address. They won't notice likely and you won't be bothered by the extra form submissions."
Filter 5: keep the junk out of Google Ads
If you run ads, this is where form spam actually costs money, and blocking it at the form does not finish the job. Your form submission fires a conversion. Smart Bidding reads conversions as evidence the click that produced them was worth buying. Every fake submission is therefore a vote for more of whatever traffic produced it, and the algorithm obliges. You are not just wasting time reading junk. You are paying to train the system to buy more of it.
The trigger is consistent: it starts when a campaign launches or is unpaused. One r/PPC commenter explained why, noting that "fresh ad URLs tend to get scraped fast, and bots flood your forms with junk submissions that look like real conversions in Google Ads." The r/Contractor operator quoted earlier saw exactly this after unpausing a campaign that had previously delivered three or four legitimate quote requests a week.
Four things to check, in order of impact:
- Turn off Search Partners and Display expansion on search campaigns. This was the most upvoted answer on the r/PPC thread and removes a large share of low quality placements.
- Set location targeting to presence, not presence or interest. It is a per campaign setting, not an account level one.
- Stop counting unqualified submissions as conversions. Do not fire the conversion tag when the honeypot tripped, the timing check failed, or the address fell outside your service area.
- Send real outcomes back with offline conversion import. This fixes the cause rather than the symptom. As one practitioner put it, either move away from a conversion focused campaign "or implement offline conversion tracking and stop sending conversion data back to Google for fake/junk leads." When bidding optimises against booked jobs instead of form fills, spam cannot steer it at all.
When the spammer is a person
If grammatical, plausible messages still arrive after all of the above, you are past bots. Two things are happening.
Click farms. One r/webdev commenter described the labour model: "Some spam companies hire humans to fill in contact forms. The humans have to video record themselves filling in X amount of contact form to get their money." An engineer from hCaptcha, on the same thread, reframed the fix as economics rather than detection. Click farms "rely on speed to make money, so the best fix is to mess with their margins", and "if it costs too much to run the attack, they'll just move on to an easier target." You are not trying to identify them, you are trying to be the annoying target. Rate limiting is the cheapest version: two submissions per IP per hour is invisible to a real customer and ruins a farm's throughput.
Solicitations. These are people selling you marketing services who found you through search. The least known fix comes from an r/webdev operator who still had spam despite reCAPTCHA and two honeypot fields: they added noindex to the contact page and "was rid of the problem for six months." Your contact page does not need to rank, since customers reach it from your home page and services pages, so removing it from the index costs you nothing that matters. Whether to run a form at all is covered in contact form vs phone number on your website.
The setup, in order
Roughly 45 minutes, ordered by return per minute.
- Add a CSS hidden honeypot named
company_website, rejected server side if filled. - Add a server side timestamp check rejecting anything submitted in under three seconds.
- Add Cloudflare Turnstile in invisible or non interactive mode.
- Reject messages containing URLs on your residential quote form.
- Require a service address and validate it against your service area.
- Score a short list of procurement phrases and spam words. Quarantine, do not delete.
- Rate limit to two submissions per IP per hour.
- Add
noindexto your standalone contact page. - Turn off Search Partners and Display expansion, and set location targeting to presence.
- Stop firing conversions on submissions that failed any check above.
Review the quarantine folder daily for a week, then weekly.
What not to do
Do not add fields to slow bots down. Every extra field costs you real inquiries, and bots fill fields faster than customers do. You are taxing the wrong party. The same goes for blocking whole countries: a landlord arranging a repair from abroad is a real job.
Do not post a legal threat next to the form. The notice charging spammers a "convenience charge" circulates in small business communities periodically. Nobody consents to anything by submitting a form, no spammer will pay it, and it is the first thing a genuine customer reads before deciding whether you are easy to deal with.
Do not remove your email address and hope. A contractor on r/Contractor stripped their email from every platform and immediately named the cost: "some people do prefer to email and i might loose a few potential jobs from it." You do not win this by being harder to contact. You win it by being harder to automate against.
The bottom line
Sort the spam before you install anything, because the pile determines the fix. Put the honeypot and the timing check on the server, where they cost your customers nothing. If you use a challenge, know that Google's free tier fails open at 10,000 assessments a month and Cloudflare's does not cap challenges at all. Then use the advantage no other industry has: your customers describe a problem at an address, and almost nobody else filling in your form does.
Once the junk stops, the question becomes which surviving leads are real, which has its own tells. The four checks that answer it are in how to tell if a lead is fake.
