How to Stop Contact Form 7 Spam Without a CAPTCHA
Contact Form 7 is on more than 5 million WordPress sites, which makes it one of the most common forms on the internet — and one of the most spammed. It is free, flexible, and utterly minimal, which is exactly why it gets hammered. Out of the box, CF7 does almost nothing to stop spam. You get an optional Akismet hook and an optional reCAPTCHA integration, and that is it.
If you have run a CF7 form for any length of time, you know the result: a steady trickle of "SEO services," crypto pitches, and gibberish landing in your inbox, day after day. Here is how to stop it properly — without bolting a CAPTCHA onto every form and watching your real enquiries drop.
Why the Usual CF7 Fixes Disappoint
Walk through the standard advice and you will find each option carries a catch.
reCAPTCHA v3 is the default recommendation, and it has two problems. First, Google cut its free tier from a million assessments a month to ten thousand in 2024, counted per Google Cloud account rather than per site — so if you run more than a couple of forms, you can blow through it. Second, and more fundamentally, reCAPTCHA scores whether the visitor looks like a bot. It has nothing to say about a real person pasting promotional garbage into your message field, and plenty of CF7 spam is exactly that.
Honeypots — hidden fields that only a bot would fill — are the classic no-friction trick, and they do catch the dumbest bots. But modern automation detects and skips them, so you are left with partial coverage and a false sense of security.
Akismet is genuinely useful and worth having, but it was built for comment spam and works on a pass/fail basis. It does not give you category-level scoring, it does not flag profanity or hate speech, and it does not let you set different sensitivity for different forms.
The common thread: every one of these asks "is this a bot?" None of them ask "is this submission actually junk?" — which is the question that matters when the spam is coming from a human or a language-model-powered bot writing convincing prose.
The Content-Level Approach
Instead of interrogating the visitor, score the submission. When someone hits submit, send the message text to a moderation API, get back scores for spam, profanity, hate speech, and junk, and decide what to do based on thresholds you set. No puzzle for the user, no hidden field for a bot to sidestep — just an evaluation of the content itself.
CF7 makes this clean because it exposes the right hook. The wpcf7_spam filter lets you mark a submission as spam before the mail is sent, which means you can plug in a content score without touching the form markup at all.
Wiring Babelshield into CF7
Drop this into your theme's functions.php or a small custom plugin:
add_filter( 'wpcf7_spam', function ( $spam, $submission ) {
if ( $spam ) {
return $spam; // already flagged by another check
}
$posted = $submission->get_posted_data();
$message = $posted['your-message'] ?? '';
if ( empty( $message ) ) {
return $spam;
}
$response = wp_remote_post( 'https://api.babelshield.com/v1/score', array(
'headers' => array(
'Authorization' => 'Bearer ' . BABELSHIELD_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array( 'text' => $message ) ),
'timeout' => 3,
) );
if ( is_wp_error( $response ) ) {
return $spam; // fail open: don't block real users if the API is unreachable
}
$scores = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ( $scores['spam'] ?? 0 ) > 0.8
|| ( $scores['profanity'] ?? 0 ) > 0.7
|| ( $scores['hate'] ?? 0 ) > 0.5 ) {
$spam = true;
}
return $spam;
}, 10, 2 );
(Endpoint, field names, and thresholds are illustrative — check the current Babelshield docs and tune to your own forms.)
A few things worth noting in that snippet, because they are the difference between a filter that helps and one that causes support tickets:
- Fail open. If the API call errors or times out, the code returns the existing spam status rather than blocking the submission. You never want a moderation outage to silently swallow real leads.
- A short timeout. Three seconds is generous; keep it tight so a slow response never leaves a user staring at a spinner.
- Thresholds, not on/off. The
0.8spam threshold is a starting point. A public contact form can run stricter; a form where customers describe technical problems (and might legitimately swear) should run looser on profanity.
Setting Sensible Thresholds
The mistake people make is treating moderation as binary. It is not. Start by logging scores without blocking anything for a week, look at where your real submissions land versus the spam, and set thresholds in the gap between them. On most contact forms, genuine enquiries score very low on spam and junk, while the garbage clusters up near the top — so there is usually a wide, safe band to draw the line in.
If you would rather not hard-block, use the score to route instead: send low-scoring submissions straight to your inbox, and quarantine high-scoring ones in a separate folder for a quick daily glance. You catch false positives without ever making a real customer resubmit.
What You Get
Once this is in place, the CF7 spam that used to reach your inbox mostly stops reaching it — including the human-written and AI-written spam that honeypots and CAPTCHAs wave straight through. Your form markup is untouched, so there is no CAPTCHA to hurt conversion and nothing extra for a real visitor to do. And because every submission is scored and logged, you finally have a record of what is hitting your forms instead of a vague sense that "there's a lot of spam."
Contact Form 7's minimalism is a feature — it stays out of your way. The trick is to add protection that stays out of your users' way too.
References
WordPress.org. Contact Form 7 plugin directory and installation statistics.
Contact Form 7 documentation. Spam filtering and the wpcf7_spam filter.
Google Cloud. reCAPTCHA billing changes, 2024.