93%
Spam en formularios
WordPress (últimos 90 días)
0%
Spam en formulario
HubSpot nativo (LPs de pago)
13
Envíos de bot
confirmados (90d)
~3d
Cadencia del bot
(1 envío cada 3-5 días)
🚨

Problema confirmado: formularios WordPress expuestos al endpoint público de HubSpot

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%.

📊 Evidencia del problema

Análisis de leads (últimos 90 días)

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%

Patrón inequívoco de bot (13 muestras confirmadas)

CampoPatrón observado
firstnameSecuencias aleatorias de teclado: dGomdaeAZrikkklvIFggAwnD, feVRVRXqnHkrKCrPxHkpUx
emailGmail con dot-aliases: t.ep.i.ba.rel.u.46.4@gmail.com, ubek.ah.eneyi.7.63@gmail.com
phoneStrings alfanuméricas no válidas: swntiLTXIrTTGCfkIJ
messageGibberish: IjwvtNKzMImMgRshlX, nJwnJmqfDUJohGozXLVVV
lastname, country, companyVací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

¿Por qué el formulario nativo de HubSpot no tiene este problema?

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.

🛡️ Stack de protección requerido
REQ 1 reCAPTCHA v3 — validación server-side Obligatorio
  • Implementar Google reCAPTCHA v3 — NO v2 (el v2 añade fricción con checkbox visible).
  • Threshold de aceptación recomendado: score ≥ 0.5
  • La validación debe hacerse server-side — no solo client-side.
  • Si 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
REQ 2 Honeypot field Obligatorio

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;
}
REQ 3 Rate-limit por IP Obligatorio

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.

  • Máximo 1 submit por IP cada 5 minutos
  • Máximo 5 submits por IP cada 24 horas
  • Implementar vía WordPress transients, Redis, o regla WAF de Cloudflare (recomendado si el site ya tiene Cloudflare).
// 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 );
REQ 4 Blocklist de emails desechables Recomendado

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.

  • Lista mantenida: github.com/disposable/disposable-email-domains (archivo domains.txt, MIT license, ~5.500 dominios)
  • Refrescar mensualmente con un cron de WordPress.
  • Mensaje de rechazo genérico: "Email no válido, usa un correo corporativo"
REQ 5 Validación MX del dominio del email Recomendado

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;
}
REQ 6 Logging de submits rechazados Obligatorio

Registrar cada submit rechazado para auditoría y ajuste de thresholds.

  • Campos mínimos: timestamp, IP, motivo (recaptcha / honeypot / disposable / rate-limit), email, página
  • Retención mínima: 30 días
  • Opciones: tabla custom en BD WordPress, log de texto en wp-content/logs/, o servicio externo (Papertrail, Loggly)
💡

Alternativa: migrar al embed oficial de HubSpot (solución más limpia)

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%.

⚠️ Campos críticos que DEBEN mantenerse

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.

CampoFunciónEstado actual
gclidID de clic de Google Ads — permite atribuir leads a campañas específicasFuncionando ✓
utm_sourceFuente de tráfico (google, bing…)No se captura en HubSpot ✗
utm_mediumMedio (cpc, organic…)No se captura en HubSpot ✗
utm_campaignCampaña de Google AdsNo se captura en HubSpot ✗
utm_contentAd ID / variante de anuncioNo se captura en HubSpot ✗

Fix adicional requerido: captura de UTMs 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.

✅ Criterios de aceptación
CriterioMediciónVentana
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
🚀 Checklist de coordinación pre-deploy
📋 Orden de implementación recomendado

Impacto esperado al completar los pasos 1-3

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.

🔧 Notas técnicas y contexto
DatoDetalle
Stack asumidoWordPress + plugin de formularios (WPForms / Contact Form 7 / Gravity Forms / Elementor) haciendo POST a forms.hsforms.com/submissions/v3/integration/submit/…
Portal HubSpotID 50866040 (Adinso)
Formulario nativo HubSpot (limpio)ID ad21188c-3856-453b-8ec3-53730e25b2bc — "Protecciones industriales - Espanol V2", alojado en solutions.adinso.eu
Páginas afectadasadinso.eu/contact (11 spam) · adinso.eu/quote (2 spam)
Por qué no basta con filtrar en HubSpotEl 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.