¡Últimas entradas para el 30 de mayo!
Andalu-SEO

Combatir el Spam de correos con Honeypots

Los honeypots representan una técnica de defensa que, aunque simple, es extremadamente efectiva en la distinción entre usuarios legítimos y bots maliciosos.

Combatir el Spam de correos con Honeypots
Autor:
Carlos Sánchez
Fecha de publicación:
2024-06-24

Última revisión:
2026-01-30

Aunque esto salga del área de SEO. Que un cliente reciba Spam de forma masiva en su correo por medio del formulario de contacto podría afectar a nuestros leads. Por no hablar que en el caso de que se haga de forma masiva, acabará consumiendo muchos recursos de nuestro servidor y por tanto ralentizando nuestra web.

Para esto existen distintas medidas de control. Algunas que son especialmente invasivas con el usuario y puede afectar a la finalización de ese lead. Es decir, si un usuario se frustra porque no puede resolver un captcha complejo, es posible que al final decida no enviar dicho formulario.

Para esto existen trucos como el Honeypot, que es una forma no invasiva de evitar el envío de formulario por parte de un bot. De hecho es la mecánica menos intrusiva.

reCaptcha v3 lo que hace es que monitorea el comportamiento del usuario en todo el sitio web (teniendo que cargar el js por toda la web para que sea efectivo), le asigna un puntaje y en base a eso decide si se puede enviar el formulario o no.

Un Honeypot bien hecho es más simple pero no por ello menos efectivo. De hecho son prácticas combinables y pueden ser usadas complementariamente para fortalecer la seguridad de un sitio web.

Así que hoy sin molestar al usuario con captchas, sin dependencias externas, y sin cargar scripts de terceros en toda la web te voy a explicar como configurar un sistema de seguridad para impedir que te lleguen los pesados correos de Spam con:

  1. Honeypot clásico: Primer filtro para bots básicos.
  2. Honeypot dinámico: Segundo filtro para bots que aprenden.
  3. Validación por tiempo: Tercer filtro para bots rápidos.
  4. Detección de patrones: Cuarto filtro para texto generado.
  5. Cookie de spam: Bloqueo persistente de reincidentes.

¿Qué es un honeypot?

Un Honeypot es una estrategia de seguridad de dejar un campo invisible para los usuarios reales, pero que los bots no se pueden contener el ansia por rellenar dicho campo. Los bots tienden a rellenar todos los campos para enviar un formulario de contacto a fin de poder mandar su spam. Cuando nosotros detectamos que dicho campo contiene información, ¡PUM! Bot invalidado.

Campo oculto de honeypot

La idea es que sea irresistible para los bots y que de esta forma al recibir nosotros información con ese campo lleno, sepamos y tengamos claro que quien nos está enviando información es un bot y no una persona real. Pues una persona real no va a desvelar dicho campo para rellenarlo.

Hay muchas formas de implementar un Honeypot, yo os voy a explicar cómo lo hago yo que es bastante eficiente y me ha funcionado bien, pero esta implementación se puede complementar y mejorar de muchas formas.

Cómo hacer un Honeypot

En primer lugar tenemos que crear un campo atractivo, pero que el usuario no pueda rellenar por error. Mi sugerencia es no ponerle un type="hidden" o un display:none; de forma inline en el propio HTML. Pues un bot puede estar programado de forma muy sencilla para ver y detectar ese campo y de esta forma no completarlo.

Cuando el bot llene la información, le añadiremos una cookie llamada "usuarioSpam" por ejemplo. Y lo podemos hacer con los siguientes pasos, empezando por crear el campo invisible:

Ejemplo:

<input type="text" name="firstname" id="calle" class="form-control sib-NAME-area" placeholder="direccion" autocomplete="nope">

Es muy importante poner el atributo de autocomplete="nope" (o cualquier valor no estándar) para evitar que se ponga información automática de un usuario por error cuando esté completando el formulario. Uso "nope" en lugar de "off" porque algunos navegadores ignoran "off" en campos con nombres comunes como "firstname".

En mi experiencia, con el type="hidden" muchos bots se evitan rellenar esa información. Aunque sea la forma nativa de HTML de ocultar un campo, mi recomendación es ocultarlo con CSS por medio de algún atributo.

Ejemplo:

[name="firstname"] {
/* Mejor que display:none - más difícil de detectar */
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}

Con esta técnica el campo sigue existiendo en el DOM (los bots lo ven), pero está fuera de la pantalla. Un display:none es más fácil de detectar programáticamente.

Ajuste de JavaScript

Si alguien hace click o le da para rellenar la información de nuestro elemento oculto, le insertamos la cookie:

document.addEventListener('DOMContentLoaded', function() {
document.querySelector('.sib-default-btn').addEventListener('click', function(event) {
var firstnameInput = document.querySelector('input[name="firstname"]');
if (firstnameInput && firstnameInput.value.trim() !== "") {
// Previene la acción por defecto si es necesario
event.preventDefault(); // Comentar si no quieres bloquear el envío
// Lógica para establecer la cookie
setCookie('usuarioSpam', '1', 30); // Establece la cookie por 30 días
location.reload();
}
});
});

document.addEventListener('DOMContentLoaded', function() {
document.body.addEventListener('submit', function(event) {
var form = event.target;
var firstnameInput = form.querySelector('input[name="firstname"]');
// Asegurarse de que el formulario contiene el input
if (firstnameInput) {
if (firstnameInput.value.trim() !== "") {
event.preventDefault(); // Previene el envío del formulario
// Establece la cookie usuarioSpam
setCookie('usuarioSpam', '1', 30); // Establece la cookie por 30 días
location.reload();
}
}
}, true);
});

Establecemos la función setCookie:

function setCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}

Ajuste de PHP

En el php para el envío de correo, donde se verifican los errores y miramos la seguridad:

Este código en un encabezado de toda la web para asegurarnos que si tiene una cookie de bot, no pueda acceder a la web:

if(isset($_COOKIE["usuarioSpam"]) && $_COOKIE["usuarioSpam"] == "1") {
// Manejar el intento como spam
die("Tu solicitud ha sido identificada como spam.");
}

Y esto para añadirle la cookie en caso de que el bot consiga enviar el formulario:

if (isset($_POST['firstname']) && !empty($_POST['firstname'])) {
setcookie("usuarioSpam", "1", time() + (86400 * 30), "/"); // Marca por 30 días
echo '<script>window.location.href="pagina_error.php";</script>';
exit;
}

Utilicemos nuestros conocimientos SEO de Renderizado a nuestro favor

Como ajuste de seguridad extra, podemos garantizarnos que sólo se envíe el formulario cuando el usuario tenga JavaScript de una forma muy simple. El botón de enviar podemos hacer que solo funcione con JavaScript con un ajuste sencillo. Haciendo que JS modifique el DOM para que sea un botón de enviar real:

<button type="button" class="cta" style="background: var(--secondary);" id="submit-form">Enviar Mensaje</button>

Este botón no hace nada por defecto, pero podemos convertirlo en un botón enviable gracias a JS:

document.addEventListener('DOMContentLoaded', function() {
// Selecciona el botón y cambia su tipo a submit
const submitBtn = document.getElementById('submit-form');
if (submitBtn) {
submitBtn.type = 'submit';
}
});

Honeypot dinámico

El honeypot clásico tiene un problema: si un bot está programado específicamente para tu web, puede aprender que el campo "firstname" es trampa y simplemente no rellenarlo.

La solución es darle una vuelta de tuerca y que el nombre del campo cambie en cada carga de página. Un bot no puede aprender algo que nunca es igual.

En lugar de tener siempre un campo llamado "firstname", generamos un nombre aleatorio en el servidor y lo guardamos en sesión para poder verificarlo después:

// En PHP, al generar el formulario:
session_start();
// Generar nombre único para este honeypot
$honeypot_name = 'hp_' . substr(md5(time() . rand()), 0, 8);
$_SESSION['honeypot_field'] = $honeypot_name;

Cada vez que alguien carga el formulario, el campo honeypot tiene un nombre diferente: hp_a3f8b2c1, hp_7d2e9f4a, hp_c8b3a1d6...

Cómo implementarlo

En el HTML del formulario:

<!-- CSS para ocultar el honeypot dinámico -->
<style>
[name="<?php echo esc_attr($honeypot_name); ?>"] {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
}
</style>
<!-- El campo honeypot dinámico -->
<input type="text"
name="<?php echo esc_attr($honeypot_name); ?>"
value=""
tabindex="-1"
autocomplete="off"
aria-hidden="true">
<!-- Puedes mantener también el honeypot clásico como doble capa -->
<input type="text"
name="firstname"
tabindex="-1"
autocomplete="nope">

Y en el PHP que procesa el formulario:

session_start();
// Verificar honeypot dinámico
if (isset($_SESSION['honeypot_field'])) {
$honeypot_name = $_SESSION['honeypot_field'];
if (isset($_POST[$honeypot_name]) && !empty($_POST[$honeypot_name])) {
// Bot detectado
die('Spam detectado.');
}
}
// Verificar honeypot clásico (doble capa)
if (isset($_POST['firstname']) && !empty($_POST['firstname'])) {
die('Spam detectado.');
}

Validación por tiempo

Un humano tarda unos segundos en leer y rellenar un formulario. Un bot lo hace en milisegundos. Podemos usar esto a nuestro favor.

Guardamos un timestamp cuando se carga el formulario. Al enviarlo, calculamos cuánto tiempo ha pasado. Si son menos de 2 segundos, es casi seguro un bot.

Implementar detección de envíos instantáneos

En el HTML, añadimos un campo oculto que JavaScript rellenará:

<input type="hidden" name="form_time" id="form_time" value="">
<script>
(function() {
// Registrar timestamp cuando se carga la página
var formTimeField = document.getElementById('form_time');
if (formTimeField) {
formTimeField.value = Math.round(Date.now());
}
})();
</script>

Y en PHP validamos:

if (isset($_POST['form_time']) && !empty($_POST['form_time'])) {
$form_time_ms = intval($_POST['form_time']);
$current_time_ms = round(microtime(true) * 1000);
$elapsed = $current_time_ms - $form_time_ms;
// Si tardó menos de 1.5 segundos, es bot
if ($elapsed < 1500) {
die('Envío demasiado rápido. Por favor, espera un momento.');
}
}

Uso 1.5 segundos (1500ms) como umbral porque es bastante permisivo para usuarios reales pero suficiente para detectar bots. Puedes ajustarlo según tu caso.

Detectar texto aleatorio de bots

Los bots más básicos rellenan los campos con texto aleatorio tipo "WdtqGvQXoCR" o "xzqprtbv". Podemos detectar estos patrones porque el texto real tiene ciertas características que el aleatorio no tiene.

Patrones sospechosos

Implementación en PHP

$nombre = trim($_POST['nombre'] ?? '');
$mensaje = trim($_POST['mensaje'] ?? '');
// === VALIDACIONES DE NOMBRE ===
// Detectar mayúsculas/minúsculas mezcladas (WdtqGvQXoCR)
$mayusculas = preg_match_all('/[A-Z]/', $nombre);
$minusculas = preg_match_all('/[a-z]/', $nombre);
if ($mayusculas >= 3 && $minusculas >= 3) {
die('El nombre no parece válido.');
}
// Detectar nombres sin vocales
if (strlen($nombre) > 4 && !preg_match('/[aeiouáéíóúAEIOUÁÉÍÓÚ]/', $nombre)) {
die('El nombre debe contener al menos una vocal.');
}
// Detectar más de 3 consonantes seguidas
if (preg_match('/[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]{4,}/', $nombre)) {
die('El nombre no parece válido.');
}
// Detectar patrones de teclado
$keyboard_patterns = ['asdf', 'qwer', 'zxcv', 'hjkl', '1234', 'abcd'];
$nombre_lower = strtolower($nombre);
foreach ($keyboard_patterns as $pattern) {
if (strpos($nombre_lower, $pattern) !== false) {
die('El nombre no parece válido.');
}
}
// === VALIDACIONES DE MENSAJE ===
// El mensaje DEBE contener espacios (palabras reales)
if (!preg_match('/\s/', $mensaje)) {
die('El mensaje debe contener palabras separadas.');
}
// Mínimo de vocales
$vocales_msg = preg_match_all('/[aeiouáéíóúAEIOUÁÉÍÓÚ]/', $mensaje);
if ($vocales_msg < 2) {
die('El mensaje debe contener texto válido.');
}

Estas validaciones no afectan a usuarios reales porque ningún nombre o mensaje legítimo falla estos tests. Pero los bots que generan texto aleatorio caen casi siempre.

Combinando todas las capas

La gracia de este sistema es que cada capa atrapa bots diferentes:

Un bot tendría que: no rellenar campos ocultos con nombres que cambian, esperar más de 2 segundos, y generar texto que parezca humano. Muy pocos lo consiguen.

Importante: Los bots modernos ya ejecutan JavaScript, por lo que las validaciones en JS son una capa extra pero no la definitiva. La validación en PHP es la que realmente importa.

Impedir Spam en Contact Form 7

En el caso de que en el formulario de contacto estés empleando el famoso plugin Contact Form 7, vamos a ver cómo implementar estas técnicas.

RECUERDA: Nunca debes probar códigos de terceros de forma directa en un entorno en producción, ni siquiera los míos. Por cualquier motivo, ya sea error humano o incompatibilidades, un código puede romper la web. Asegúrate de probarlo primero en un entorno seguro.

La forma más limpia de implementar esto es creándote un plugin propio que añada la funcionalidad. Así no tocas el código de CF7 y las actualizaciones no te rompen nada.

Crear el campo honeypot en Contact Form 7

Primero, necesitamos añadir un campo que actuará como honeypot. Este campo será visible para los bots pero invisible para los usuarios humanos, usando CSS para ocultarlo efectivamente.

[text* firstname id:firstname class:form-control placeholder "direccion" autocomplete:nope]

Este campo, aunque no es de tipo hidden, será ocultado usando CSS. Lo designamos con un placeholder inofensivo como "dirección" para engañar a los bots.

Plugin completo con todas las capas

Crea un archivo en wp-content/plugins/mi-antispam-cf7/mi-antispam-cf7.php:

<?php
/*
Plugin Name: {Lo que quieras}
Description: {Lo que quieras}
Version: 1.0
*/
// Iniciar sesión para el honeypot dinámico
add_action('init', function() {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
});
// Generar honeypot dinámico y añadir CSS/JS
add_action('wp_footer', function() {
// Generar nombre único
$hp_name = 'hp_' . substr(md5(time() . rand()), 0, 8);
$_SESSION['cf7_honeypot'] = $hp_name;
$_SESSION['cf7_form_loaded'] = time();
?>
<style>
#firstname, [name="<?php echo esc_attr($hp_name); ?>"] {
position: absolute !important;
left: -9999px !important;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Inyectar honeypot dinámico en formularios CF7
var forms = document.querySelectorAll('.wpcf7-form');
forms.forEach(function(form) {
var hp = document.createElement('input');
hp.type = 'text';
hp.name = '<?php echo esc_js($hp_name); ?>';
hp.tabIndex = -1;
hp.autocomplete = 'off';
form.appendChild(hp);
// Añadir timestamp
var ts = document.createElement('input');
ts.type = 'hidden';
ts.name = 'form_timestamp';
ts.value = Date.now();
form.appendChild(ts);
});
});
</script>
<?php
});
// Validar antes de enviar
add_filter('wpcf7_validate', function($result, $tags) {
// 1. Honeypot clásico
if (!empty($_POST['firstname'])) {
$result->invalidate('', 'Spam detectado.');
return $result;
}
// 2. Honeypot dinámico
if (isset($_SESSION['cf7_honeypot'])) {
$hp_name = $_SESSION['cf7_honeypot'];
if (!empty($_POST[$hp_name])) {
$result->invalidate('', 'Spam detectado.');
return $result;
}
}
// 3. Validación por tiempo
if (!empty($_POST['form_timestamp'])) {
$elapsed = (time() * 1000) - intval($_POST['form_timestamp']);
if ($elapsed < 1500) { $result->invalidate('', 'Por favor, tómate tu tiempo.');
return $result;
}
}
// 4. Detectar texto aleatorio en nombre
$nombre = $_POST['your-name'] ?? '';
// Sin vocales
if (strlen($nombre) > 4 && !preg_match('/[aeiouáéíóú]/i', $nombre)) {
$result->invalidate('your-name', 'Introduce un nombre válido.');
return $result;
}
// Muchas consonantes seguidas
if (preg_match('/[bcdfghjklmnpqrstvwxyz]{4,}/i', $nombre)) {
$result->invalidate('your-name', 'Introduce un nombre válido.');
return $result;
}
return $result;
}, 10, 2);

Usar el shortcode con honeypot incluido

También puedes crear un shortcode personalizado que envuelva el formulario de CF7:

function custom_contact_form_shortcode($atts) {
$atts = shortcode_atts(array(
'id' => '',
), $atts);
$output = '';
// CSS para ocultar el campo honeypot
$css = '<style>
#firstname {
position: absolute;
left: -9999px;
}
</style>';
if (!empty($atts['id'])) {
$output .= $css;
$output .= do_shortcode('[contact-form-7 id="' . esc_attr($atts['id']) . '"]');
}
return $output;
}
add_shortcode('custom_contact_form', 'custom_contact_form_shortcode');

En tu página de WordPress, añade:

[custom_contact_form id="ID_del_formulario"]

Reemplaza "ID_del_formulario" con el ID real de tu formulario de Contact Form 7.

Validación adicional del lado del servidor

No olvides que también es importante manejar la validación del lado del servidor. Si detectas que el campo honeypot está lleno cuando se envía el formulario, debes bloquear esa solicitud ya que es indicativo de actividad de bot.

Añade a functions.php para manejar la validación básica:

function check_honeypot_field() {
if (!empty($_POST['firstname'])) {
wp_die('Error: Se detectó actividad de spam.');
}
}
add_action('init', 'check_honeypot_field');

Este script de PHP intercepta las peticiones antes de que WordPress las procese completamente, verificando si el campo honeypot está lleno y, en caso afirmativo, termina la ejecución y devuelve un mensaje de error.

Banner promocionado:

Raiola Networks

Conclusión

Implementar un Honeypot dinámico con validación de tiempo es, en mi experiencia, la forma más equilibrada de mantener la web limpia sin castigar al usuario con captchas infumables que además dependen de terceros. Ganas en velocidad evitando cargar scripts pesados y en salud.

Pero no nos engañemos: esto no es una solución definitiva ni sustituye a una seguridad de servidor seria. Hay que entender que el Honeypot es solo una capa más.

Mientras nosotros filtramos quién pasa por el formulario, esta estrategia solo funciona si el Spam llega por medio de los formularios de tu web.

Si es porque han añadido tu correos en listas indeseadas, hay otras tras herramientas como los RBL (listas negras de IPs) o un buen Firewall actúan mucho antes, bloqueando el tráfico basura antes de que llegue siquiera a oler tu WordPress.

Si quieres saber si una cuenta de email está donde no debería, puedes comprobarlo aquí: https://haveibeenpwned.com/

Nunca existe nada 100% seguro y además la seguridad no debe ser incompatible con la comodidad. No tiene sentido que en nuestra casa pusiéramos un foso con cocodrilos. Todo es hackeable, todo es Spameable y la idea es ponerlo más difícil de lo habitual.

Si tenemos un sistema propio nos ahorramos si alguien descubre la forma de saltarse el captcha de Google o el que usemos, tenemos control y personalización y además es nuestro propio script.

Si te gusta este artículo, me ayudarías un montón compartiendo mi contenido:
No se te da mal el SEO Técnico

Te falta mi máster. Accede a una formación avanzada que te permitirá aplicar e implementar SEO en cualquier tipo de WEB

¡Accede al Máster de SEO Técnico!
Tal vez te interesen otros artículos:
Artículos de SEO

Si te ha gustado esta publicación, siempre me lo puedes agradecer dándome like en esta publicación de LinkedIn sobre este mismo artículo.

Usamos cookies para asegurar que te damos la mejor experiencia en nuestra web. Aquí tienes nuestra política de Cookies.