Operación6 min

Auditamos nuestro propio formulario y podía mandarle WhatsApp a cualquiera

Auditamos el formulario de nuestro sitio y encontramos que un solo POST disparaba seis acciones externas. Qué revisar en el tuyo y cómo cerrarlo.

De dónde salen los datos

Auditoría del código de aifennecia.com del 2026-08-20, hecha con un agente revisor independiente y verificada a mano contra producción. Todo lo que se describe aquí estaba en nuestro propio sitio y ya está corregido.

Por Wilmar Rocha

Hay una pregunta que casi nadie le hace a su formulario de contacto: ¿qué pasa exactamente cuando alguien lo envía?

No cuántos leads llegan. Qué ocurre. Qué sistemas se mueven, cuánto cuesta cada envío, y quién puede provocarlo.

Nosotros se la hicimos al nuestro esta semana. La respuesta no nos gustó.

Un solo envío movía seis cosas

El formulario de nuestro sitio hace lo que hace cualquier embudo bien montado: cuando alguien lo completa, se dispara la operación. En nuestro caso, seis acciones:

  1. Crea el contacto en el CRM
  2. Crea la oportunidad en el pipeline
  3. Reporta la conversión al píxel de Meta
  4. Reporta la conversión a Google
  5. Manda una alerta interna
  6. Envía una plantilla de WhatsApp al número que venga en el formulario

Los cinco primeros son internos: si alguien los ensucia, el daño se limita a nuestros datos. El sexto es distinto. El sexto le escribe a un tercero.

Y en cierta configuración, un séptimo: una llamada telefónica automatizada.

Por qué eso importa más de lo que parece

El endpoint que recibe el formulario es público. Tiene que serlo: es un formulario. Cualquiera en internet puede mandarle datos, y no hace falta abrir el navegador — basta una línea de comando.

Nuestra única defensa era un honeypot: un campo oculto que un humano nunca llena y un bot tosco sí. Sirve contra el bot tosco. No sirve contra nadie que dedique diez minutos a mirar cómo funciona el formulario.

Sin ningún freno adicional, alguien podía repetir ese envío en bucle con números ajenos. El resultado:

  • Cada mensaje se paga. WhatsApp Business cobra por plantilla enviada.
  • Meta penaliza la plantilla. Cuando la gente que recibe mensajes que no pidió los reporta, la plantilla baja de calidad y termina bloqueada. No es una multa: es que tu canal de WhatsApp deja de funcionar para los clientes reales.
  • El CRM se llena de basura, y con él los reportes con los que decides.
  • Las conversiones falsas envenenan la pauta. Este es el daño silencioso y el más caro: si le reportas a Meta cien conversiones falsas, Meta aprende a buscar más gente parecida a quien las generó. El algoritmo optimiza hacia el ruido, y tú lo pagas en CPL durante semanas sin entender por qué subió.

Ese último punto es el que la mayoría no ve venir. Un formulario sin freno no es solo un riesgo de seguridad: es un riesgo de presupuesto de pauta.

Lo segundo que encontramos: la validación que se apagaba sola

El otro hallazgo era más sutil, y es un patrón que hemos visto en varios proyectos ajenos.

La verificación de firma de los webhooks de Meta estaba escrita así:

if (APP_SECRET) {
    // ...verificar la firma...
}

Se lee como algo defensivo. Es lo contrario.

Si la variable de entorno APP_SECRET no existe, el bloque entero se salta y el endpoint acepta cualquier cosa que le llegue. Sin error. Sin alerta. Sin nada en los logs que lo delate.

Y no existía. Nunca se configuró en el servidor. Comprobamos en producción: una petición sin firma recibía un 200 OK tan tranquila.

A este patrón se le llama fail-open: ante la duda, abrir. Lo correcto es lo opuesto — si falta el secreto, el endpoint debe rechazar todo y quejarse fuerte, no seguir como si nada.

Cómo revisar si lo tienes: busca en tu código validaciones que estén dentro de un if que dependa de una variable de entorno. Cada una es un control que se apaga solo el día que alguien olvide configurar esa variable — o el día que un despliegue la pierda.

Qué hicimos, en orden

El orden importa más que los arreglos, y es donde es fácil hacerse daño solo.

Primero, poner el secreto que faltaba. Esto parece trivial y tiene una trampa: poner un valor cualquiera es peor que dejarlo vacío. El código activa la validación en cuanto la variable existe, así que con una clave equivocada Meta empieza a recibir rechazos, reintenta, y termina desuscribiendo el webhook. Nos pasó: guardamos un texto de ejemplo por error y quedó armado hasta que lo reemplazamos.

Antes de desplegarlo lo verificamos contra la API de Meta, que confirma si la pareja aplicación + secreto es válida. Treinta segundos de comprobación que evitan un bot caído sin explicación.

Segundo, cerrar la validación. Ya con el secreto real puesto, la firma pasa a ser obligatoria. Probado: firma válida entra, firma alterada se rechaza, sin firma se rechaza, y sin secreto el endpoint responde error en vez de procesar.

Tercero, frenar el formulario. Dos capas, y la segunda es la que de verdad protege:

  • Un límite por origen, que corta el bucle simple.
  • Un techo por hora sobre las acciones que cuestan dinero. Aunque alguien rote direcciones, el gasto queda acotado. No impide el abuso: lo limita. Esa distinción es honesta y es la que importa cuando el atacante tiene más paciencia que tú.

Cuarto, y esto casi nos cuesta más que el problema: el primer límite que pusimos era demasiado estricto. En Colombia el internet móvil sale por CGNAT — barrios enteros comparten una misma dirección pública. Un límite bajo le habría negado el formulario a leads reales durante una campaña con tráfico móvil, cerrándoles el único paso que convierte.

Perder un lead pagado cuesta más que el abuso que ese límite evitaba. Lo subimos, y en su lugar reforzamos lo que no castiga a nadie legítimo.

El error que casi cometemos con el reintento

Un último hallazgo, y es el que más dinero mueve.

El formulario tenía una protección contra doble clic. Buena. Pero se activaba después de recibir respuesta del servidor.

Piensa en el caso real: el servidor procesa el lead correctamente, y justo entonces la conexión del usuario se cae. Él ve un error. Vuelve a enviar. Para el servidor son dos leads distintos: dos oportunidades, dos plantillas de WhatsApp, dos conversiones reportadas.

En una red móvil eso no es una hipótesis, es un martes cualquiera.

La solución no es de interfaz, es de servidor: el mismo correo y teléfono dentro de una ventana corta se responde con éxito, pero sin repetir las acciones. El usuario avanza igual y nada se cobra dos veces.

Los cinco minutos que te ahorran el susto

Si tienes un formulario conectado a un CRM, a WhatsApp o a un píxel, revisa esto:

¿Qué dispara un envío hacia afuera? Haz la lista. Marca cuáles cuestan dinero y cuáles le escriben a un tercero. Esa segunda columna es tu superficie real de riesgo.

¿Hay algún freno? Si la única defensa es un campo oculto, no hay freno.

¿Alguna validación vive dentro de un if de variable de entorno? Si sí, está apagada en cualquier servidor donde esa variable falte.

¿Un reintento crea un duplicado? Pruébalo: envía el mismo lead dos veces y mira si aparecen dos oportunidades. Si aparecen, tus conversiones están infladas y tu pauta está optimizando con datos torcidos.

¿El formulario le responde mal a un lead legítimo bajo carga? Un freno mal calibrado se ve igual que un formulario roto — y no te enteras, porque el que no pudo enviar no te escribe para avisarte.


Publicamos esto con nuestros propios errores adentro por una razón práctica: los cuatro problemas de arriba los hemos encontrado después en sitios de clientes, y en todos los casos llevaban meses ahí. Nadie los había visto porque el formulario funcionaba. Recibía leads. Se veía bien.

Un formulario que funciona y un formulario que está bien montado no son lo mismo, y la diferencia solo aparece cuando alguien va a mirar.

Seguir leyendo