Los formularios en adinso.eu/contact y adinso.eu/quote hacen POST directo al API de HubSpot (forms.hsforms.com/submissions/v3/…) sin ninguna protección anti-spam. Resultado: 93% de los leads en HubSpot son bots. Los leads de pago (Google Ads → solutions.adinso.eu) están limpios al 100%.
| Origen | Total contacts | Spam | Tasa de spam |
|---|---|---|---|
| adinso.eu/contact (WordPress) | 11 | 11 | 100% |
| adinso.eu/quote (WordPress) | 2 | 2 | 100% |
| solutions.adinso.eu (HubSpot nativo, LPs de pago) | 9 | 0 | 0% |
| Campo | Patrón observado |
|---|---|
firstname | Secuencias aleatorias de teclado: dGomdaeAZrikkklvIFggAwnD, feVRVRXqnHkrKCrPxHkpUx |
email | Gmail con dot-aliases: t.ep.i.ba.rel.u.46.4@gmail.com, ubek.ah.eneyi.7.63@gmail.com |
phone | Strings alfanuméricas no válidas: swntiLTXIrTTGCfkIJ |
message | Gibberish: IjwvtNKzMImMgRshlX, nJwnJmqfDUJohGozXLVVV |
lastname, country, company | Vacíos en todos los envíos de bot |
| Cadencia | ~1 envío cada 3-5 días (diseñado para evadir rate-limits por ráfaga) |
| Form source en HubSpot | "Unidentified Form" o "#contact_form" — confirma que NO es el form nativo de HubSpot |
El embed oficial de HubSpot (js.hsforms.net/forms/embed/v2.js) incluye protecciones nativas: reCAPTCHA, honeypot, y rate-limiting gestionados por HubSpot. Los formularios WordPress custom que hacen POST directo a forms.hsforms.com/submissions/v3/… omiten completamente estas capas.
score < 0.5: rechazar el submit, devolver HTTP 400, NO reenviar a HubSpot.// PHP — validar token reCAPTCHA v3 antes de enviar a HubSpot
$token = sanitize_text_field( $_POST['recaptcha_token'] );
$secret = 'TU_RECAPTCHA_SECRET_KEY';
$response = wp_remote_post( 'https://www.google.com/recaptcha/api/siteverify', [
'body' => [ 'secret' => $secret, 'response' => $token ],
] );
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! $data['success'] || $data['score'] < 0.5 ) {
wp_send_json_error( [ 'message' => 'Verificación fallida' ], 400 );
exit;
}
// Solo si score >= 0.5: continuar y reenviar a HubSpot
Campo oculto invisible para humanos pero visible para bots. Si llega con valor → es bot. Rechazar submit.
<!-- Añadir dentro del <form>, justo antes del submit -->
<div style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden"
aria-hidden="true" tabindex="-1">
<label for="website_url">Dejar vacío</label>
<input type="text" name="website_url" id="website_url"
autocomplete="off" tabindex="-1">
</div>
// Server-side: rechazar si honeypot viene con valor
if ( ! empty( $_POST['website_url'] ) ) {
wp_send_json_error( [], 400 );
exit;
}
El bot actual envía ~1 vez cada 3-5 días para evadir rate-limits por ráfaga. Implementar ventana de 24h además del límite de 5 min.
// Implementación con WordPress transients
$ip_key_5m = 'form_rate_' . md5( $_SERVER['REMOTE_ADDR'] ) . '_5m';
$ip_key_24h = 'form_rate_' . md5( $_SERVER['REMOTE_ADDR'] ) . '_24h';
$count_5m = (int) get_transient( $ip_key_5m );
$count_24h = (int) get_transient( $ip_key_24h );
if ( $count_5m >= 1 || $count_24h >= 5 ) {
wp_send_json_error( [ 'message' => 'Demasiados intentos. Intenta más tarde.' ], 429 );
exit;
}
set_transient( $ip_key_5m, $count_5m + 1, 5 * MINUTE_IN_SECONDS );
set_transient( $ip_key_24h, $count_24h + 1, DAY_IN_SECONDS );
Nota: El bot actual usa Gmail dot-aliases — no dominios desechables típicos. Esta capa es útil como defensa adicional contra futuros bots con otros patrones.
domains.txt, MIT license, ~5.500 dominios)Verificar que el dominio del email tenga un registro MX válido (DNS lookup). Rechazar si no hay MX.
$email = sanitize_email( $_POST['email'] );
$domain = substr( strrchr( $email, '@' ), 1 );
$mx = [];
if ( ! checkdnsrr( $domain, 'MX' ) ) {
wp_send_json_error( [ 'message' => 'Email no válido' ], 400 );
exit;
}
Registrar cada submit rechazado para auditoría y ajuste de thresholds.
timestamp, IP, motivo (recaptcha / honeypot / disposable / rate-limit), email, páginawp-content/logs/, o servicio externo (Papertrail, Loggly)Si el stack actual lo permite, la opción más limpia es reemplazar los formularios WordPress custom por el embed nativo de HubSpot:
<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"></script>
<script>
hbspt.forms.create({
region: "na1",
portalId: "50866040",
formId: "ad21188c-3856-453b-8ec3-53730e25b2bc"
});
</script>
Esto hereda automáticamente: reCAPTCHA, honeypot, rate-limiting y todos los controles anti-spam de HubSpot — sin código adicional. Es el mismo embed que usa solutions.adinso.eu con tasa de spam del 0%.
Los formularios actuales tienen campos hidden para atribución de Google Ads. Estos campos son críticos para PPC — NO eliminarlos en ninguna migración.
| Campo | Función | Estado actual |
|---|---|---|
gclid | ID de clic de Google Ads — permite atribuir leads a campañas específicas | Funcionando ✓ |
utm_source | Fuente de tráfico (google, bing…) | No se captura en HubSpot ✗ |
utm_medium | Medio (cpc, organic…) | No se captura en HubSpot ✗ |
utm_campaign | Campaña de Google Ads | No se captura en HubSpot ✗ |
utm_content | Ad ID / variante de anuncio | No se captura en HubSpot ✗ |
Los 8 leads de pago de Google Ads tienen gclid en HubSpot pero los campos utm_source/medium/campaign/content están vacíos. El formulario no está leyendo los query params de la URL y mapeándolos a propiedades de contacto en HubSpot.
Fix: Añadir un script JS en solutions.adinso.eu que lea window.location.search, extraiga los UTM params y los escriba en los campos hidden del formulario antes del submit. Esto es independiente del anti-spam pero es necesario para atribución granular.
| Criterio | Medición | Ventana |
|---|---|---|
| Tasa de spam < 5% | % de submits que llegan a HubSpot con patrón de bot (firstname gibberish / phone alfanumérico) | 2 semanas post-deploy |
| 0 falsos positivos | Ningún humano legítimo bloqueado (medible vía email de soporte / contacto directo) | 2 semanas post-deploy |
| Campos UTM capturados | utm_source, utm_medium, utm_campaign poblados en ≥ 80% de los leads de pago | Tras el fix de UTM capture |
gclid, utm_source, utm_medium, utm_campaign, utm_content siguen presentes y funcionando.Los tres primeros pasos juntos deberían eliminar >95% del spam actual. El bot identificado no es sofisticado: no supera reCAPTCHA v3, y el honeypot + rate-limit 24h lo bloquean por diseño.
| Dato | Detalle |
|---|---|
| Stack asumido | WordPress + plugin de formularios (WPForms / Contact Form 7 / Gravity Forms / Elementor) haciendo POST a forms.hsforms.com/submissions/v3/integration/submit/… |
| Portal HubSpot | ID 50866040 (Adinso) |
| Formulario nativo HubSpot (limpio) | ID ad21188c-3856-453b-8ec3-53730e25b2bc — "Protecciones industriales - Espanol V2", alojado en solutions.adinso.eu |
| Páginas afectadas | adinso.eu/contact (11 spam) · adinso.eu/quote (2 spam) |
| Por qué no basta con filtrar en HubSpot | El skill adinso-spam-scorer del lado de HubSpot es un parche temporal. La solución de raíz es cortar el spam antes de que entre al CRM. |