Stopping Drupal Webform Spam: Beyond Honeypot and CAPTCHA
Drupal punches well above its weight. It runs a small share of all websites but a meaningful chunk of the high-traffic, enterprise, government, and higher-education web — the sites where a form submission might be a citizen request, a patient enquiry, or a six-figure procurement lead. The Webform module is the workhorse behind most of those forms, and it is genuinely excellent. What it is not is well defended against modern spam.
The standard Drupal anti-spam stack is Honeypot, Antibot, and the CAPTCHA/reCAPTCHA modules. They are worth having. But every one of them is trying to answer the same question — is this an automated submission? — and on the kind of forms Drupal tends to power, that is only half the problem.
Where the Standard Modules Stop
Honeypot adds a hidden field and a timing check. It catches naïve bots and nothing else; anything that renders the page and behaves like a browser walks past it.
Antibot requires JavaScript to submit, which filters out the laziest scripts but does nothing about headless browsers or the automation that now drives most serious spam.
CAPTCHA / reCAPTCHA adds friction for humans and, as the research keeps showing, gets solved by bots at high rates anyway. On a public-sector or accessibility-sensitive Drupal site, a CAPTCHA is also an accessibility liability you may not be allowed to ship.
None of them look at what was typed. A Webform that accepts a free-text message can receive spam, profanity, hate speech, or a phishing URL from a real human, and the entire standard stack will record it as a valid submission. For a site that stores submissions, emails them to staff, or displays them anywhere, that is exactly the content you do not want in your system.
The Handler Approach
Webform's extensibility is the answer. The module lets you attach handlers to a form — plugins that run at defined points in the submission lifecycle. A custom handler can call a moderation API during validation, read back category scores, and reject or flag the submission before it is ever saved.
Here is the shape of a handler that scores submissions with Babelshield:
// modules/custom/babelshield/src/Plugin/WebformHandler/BabelshieldHandler.php
namespace Drupal\babelshield\Plugin\WebformHandler;
use Drupal\Core\Form\FormStateInterface;
use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\webform\WebformSubmissionInterface;
/**
- @WebformHandler(
- id = "babelshield",
- label = @Translation("Babelshield content moderation"),
- category = @Translation("Spam protection"),
- submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_SINGLE,
- )
*/
class BabelshieldHandler extends WebformHandlerBase {
public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {
$data = $webform_submission->getData();
$text = trim(implode(' ', array_filter($data, 'is_string')));
if ($text === '') {
return;
}
try {
$scores = \Drupal::service('babelshield.client')->score($text);
}
catch (\Exception $e) {
// Fail open: don't block legitimate users if the service is unreachable.
\Drupal::logger('babelshield')->warning($e->getMessage());
return;
}
if (($scores['spam'] ?? 0) > 0.8
|| ($scores['profanity'] ?? 0) > 0.7
|| ($scores['hate'] ?? 0) > 0.5) {
$form_state->setErrorByName('', $this->t('Your submission could not be processed. Please review your message and try again.'));
}
}
}
The babelshield.client service is a thin wrapper around an HTTP call to the scoring endpoint — the kind of small service you would register in your module's *.services.yml. (Endpoint, field names, and thresholds are illustrative — check the current Babelshield docs and tune to your forms.)
Why Validation, Not Just Storage
Scoring during validateForm() gives you a choice that a purely post-hoc filter does not. You can:
- Block outright by setting a form error, so the submission is never stored and never emailed — right for high-risk public forms.
- Flag and store by letting the submission through but writing the scores into a submission field, so moderators can triage in the Webform results UI — right where you need an audit trail more than a hard wall.
- Route by score, sending clean submissions straight into your workflow while holding flagged ones for review.
For regulated Drupal sites — health, government, education — the flag-and-store pattern is often the safer default. You keep every submission and every decision on record, which is exactly what an auditor wants to see, while still keeping the abusive content away from the staff who process the queue.
Fail Open, Log Everything
Two rules carry over from any serious moderation integration, and they matter even more on the mission-critical forms Drupal tends to run. First, fail open: if the API is unreachable, let the submission through rather than blocking a real citizen or customer over a network blip — and log it so you know it happened. Second, log the scores on every submission, not just the blocked ones. That log is what lets you tune thresholds with real data and prove, later, why any given submission was handled the way it was.
The Result
A Webform handler turns Drupal's biggest spam blind spot — the content of the submission — into a scored, logged, configurable checkpoint. You keep Honeypot and Antibot for what they are good at, drop the CAPTCHA that was hurting your accessibility and your conversions, and add a layer that actually reads what people send you. On the kind of high-stakes forms Drupal is built for, that is the layer that was missing.
References
Drupal.org. Webform module documentation and handler plugin API.
Drupal.org. Honeypot, Antibot, and CAPTCHA module documentation.
BuiltWith; W3Techs. Drupal usage and enterprise-tier share, 2026.