Estoy completamente atrapado intentando verificar mi webhook de la API de Meta/WhatsApp Business. Meta sigue rechazándolo con el error estándar: “La URL de devolución de llamada o el token de verificación no pudieron validarse”.
He aislado el problema a cómo n8n está devolviendo la respuesta, pero no puedo averiguar por qué está fallando.
Aquí está mi configuración:
Alojamiento: n8n autohospedado a través de Docker (Windows).
Túnel: Cloudflare Tunnels (.trycloudflare.com).
Nodo Trigger: Nodo Webhook, Método GET, Ruta whatsapp-incoming. La opción Respond está configurada estrictamente en “Using ‘Respond to Webhook’ Node”.
Nodo de Respuesta: Nodo ‘Respond to Webhook’. Respond With está configurado en Text. La expresión Response Body es exactamente: {{ $json.query["hub.challenge"] }}.
Aquí está el comportamiento extraño que estoy experimentando:
La red está bien: La solicitud de Meta llega correctamente a mi instancia de n8n.
La ejecución tiene éxito: En la pestaña de Executions, el flujo de trabajo se completa perfectamente y muestra un estado verde “Succeeded”.
La expresión se analiza correctamente: Cuando verifico los detalles de ejecución del nodo ‘Respond to Webhook’, captura y muestra correctamente el número de desafío exacto enviado por Meta.
EL PROBLEMA (Fallo silencioso): Cuando pruebo la URL manualmente a través de mi navegador (p. ej., https://my-cloudflare-url/webhook/whatsapp-incoming?hub.challenge=11223344), n8n registra una ejecución exitosa, pero el navegador muestra una pantalla completamente en blanco. No devuelve el texto, ni siquiera un mensaje de error. Devuelve un cuerpo vacío.
Debido a que el cuerpo de la respuesta está vacío en lugar de devolver el número de desafío sin procesar, Meta rechaza la verificación.
Ya he intentado:
Eliminar todos los Response Headers adicionales en el nodo ‘Respond to Webhook’.
Asegurarme de que la expresión se evalúe como una Expression (fx) y no como una cadena estática.
Recrear el nodo webhook.
¿Alguien ha experimentado este problema donde el nodo ‘Respond to Webhook’ se ejecuta correctamente pero devuelve un cuerpo vacío al cliente? ¿Me estoy perdiendo alguna configuración?
¡Hola @Permadi! ¡Bienvenido a la comunidad de n8n!
Creo que tu configuración es correcta, así que no necesitas cambiar a JSON ni alterar URLs. Probé exactamente tu configuración y funciona perfectamente.
La confusión viene del panel de salida. Por defecto, el nodo ‘Respond to Webhook’ muestra su entrada, no la respuesta real que envía. Si tu expresión se resuelve como vacía, n8n sigue registrando una ejecución verde exitosa (HTTP 200) pero envía un cuerpo en blanco. Esto coincide exactamente con tu problema.
Así que abre el nodo Respond, ve a la pestaña Settings y activa Enable Response Output Branch para ver qué se envió. Si está vacío, la expresión no se está resolviendo (a menudo debido a comillas rizadas copiadas), así que reescribe los corchetes a mano. Si muestra 11223344 y el navegador sigue sin recibir nada, entonces es el túnel trycloudflare quien está descartando el cuerpo y esa es una búsqueda diferente.
¡Hola @Permadi! ¡Bienvenido!
Responder Con: Text envía Content-Type: text/html de forma predeterminada, y el handshake de Meta quiere el desafío devuelto como texto plano sin nada envuelto alrededor. Eliminar los encabezados de respuesta lo dejó en ese valor predeterminado, así que establécelo explícitamente en el nodo Respond to Webhook, Name Content-Type y Value text/plain.
Mira esto:
La pista en tu publicación que las respuestas anteriores pasan por alto es esta: tu vista previa de ejecución muestra el número de desafío, pero el cuerpo llega vacío. Eso descarta la teoría de las comillas curvas, porque si la expresión no se resolviera no verías el número en la vista previa en absoluto. Y tampoco se explica completamente por el tipo de contenido, ya que text/html seguiría enviando el número, solo con el encabezado incorrecto, no un cuerpo en blanco.
Lo que se ajusta a toda tu evidencia es el tipo. hub.challenge llega como un número, y Responder a Webhook en modo Texto quiere una cadena. Cuando recibe un número desnudo, algunas compilaciones no envían nada, que es exactamente tu ejecución verde con un cuerpo vacío.
Fuérzalo a una cadena y prueba de nuevo:
{{ $json.query[“hub.challenge”].toString() }}
o
{{ String($json.query[“hub.challenge”]) }}
Si el navegador ahora muestra el número, era eso, y Meta aceptará el apretón de manos. Establece el encabezado Content-Type en text/plain como dijo Anshul mientras estés ahí, ya que Meta quiere texto plano una vez que el cuerpo no esté vacío.
Si sigue en blanco después de convertir a una cadena, entonces el túnel se está comiendo el cuerpo en lugar de n8n. Los túneles rápidos de trycloudflare pierden cuerpos pequeños intermitentemente, así que golpea la URL con curl -i directamente contra tu contenedor, omitiendo Cloudflare. Si curl muestra el número y el navegador a través del túnel no, tienes tu respuesta y no es n8n en absoluto.