Un Route Handler de Next.js en Webflow Cloud envía cada formulario directamente a HubSpot, manteniendo el mapeo de campos en tu repositorio bajo control de versiones.
La configuración habitual de Webflow a HubSpot dirige cada envío a través de una herramienta de automatización como Zapier. Cuando se envía un formulario, se activa un flujo de trabajo automatizado que envía los datos a HubSpot basándose en un mapeo de campos configurado previamente.
Cuando un diseñador cambia el nombre de un campo en el Designer, el mapeo externo puede desincronizarse y dejar de enviar ese campo a HubSpot. El mapeo reside fuera del repositorio y fuera del despliegue que modificó el formulario.
Webflow Cloud te permite integrar ese paso directamente en el sitio. Creas un Route Handler de Next.js, montado en el mismo dominio que tus páginas de Webflow, que recibe el envío del formulario y lo reenvía al endpoint de HubSpot, tras realizar un par de comprobaciones.
El mapeo de campos se convierte en una función de TypeScript en un repositorio, lo que te permite coordinar los cambios de nombres de campos en el Designer con el despliegue del mapeo correspondiente.
¿Qué necesitas para conectar un formulario de Webflow a HubSpot en Webflow?
Necesitas seis requisitos previos, incluido Webflow Cloud, disponible desde el plan Starter gratuito en adelante; montar la aplicación en un dominio personalizado requiere el plan Premium o superior.
Confirma el proyecto, la cuenta, el entorno de ejecución, la ruta de montaje y el acceso al entorno antes de empezar:
- Un sitio de Webflow con Webflow Cloud disponible: Webflow Cloud está incluido desde el plan Starter gratuito en adelante, y montar la aplicación en un dominio personalizado requiere el plan Premium o superior.
- Una cuenta de HubSpot con un formulario propio: Necesitas su ID de portal, el GUID del formulario y los nombres internos exactos de las propiedades, porque HubSpot rechaza un envío que incluya un campo que el formulario no define, en lugar de ignorar un valor cuyo nombre de entrada no coincida.
- Una versión compatible de Node.js y npm: La documentación de Webflow Cloud especifica la versión de Node.js compatible, y npm es el único gestor de paquetes que admite; los comandos pnpm y yarn no funcionan allí.
- Un proyecto de Next.js compatible: Los despliegues de aplicaciones propias requieren una versión de Next.js compatible, por lo que es posible que un proyecto antiguo necesite una actualización previa.
- Decisión sobre la ruta de montaje: Elige la ruta donde residirá la aplicación en tu sitio, como
/forms, antes de configurar el script del navegador. - Acceso a la configuración del entorno de Webflow Cloud: Las variables de entorno, incluidas las credenciales de HubSpot, se establecen por entorno, así que acuerden desde el principio quién posee el token y quién puede rotarlo.
Con estos requisitos previos, puedes construir la ruta basándote en los nombres de las propiedades de HubSpot, el endpoint de envío y la ruta de montaje que llamará tu formulario de Webflow.
6 pasos para enviar un formulario de Webflow a HubSpot en Webflow Cloud
Enviar el formulario requiere seis pasos: preparar HubSpot, crear la estructura de la aplicación, escribir el manejador, configurar su entorno, conectar el formulario de Webflow y probar la ruta montada de principio a fin.
Completa la configuración de HubSpot y de la aplicación antes de conectar el formulario publicado.
1. Crea el formulario de HubSpot y obtén su endpoint de envío
Crea el formulario en HubSpot con exactamente las propiedades que quieres que se rellenen desde Webflow, luego anota su endpoint de envío y el nombre interno de cada propiedad. HubSpot hace coincidir los valores enviados según el nombre interno de la propiedad.
Una etiqueta como "Nombre" no tiene importancia para la integración; el nombre interno es lo que escribirás más adelante en el atributo "name" del campo de entrada en Webflow. Si el endpoint de HubSpot requiere un token de aplicación privada en la configuración de tu cuenta, genéralo ahora y guárdalo en un lugar al que las personas adecuadas tengan acceso.
HubSpot no te entrega una URL; tú la construyes a partir del ID del portal y el GUID del formulario, y puedes elegir entre dos:
POST https://api.hsforms.com/submissions/v3/integration/submit/{portalId}/{formGuid}
POST https://api.hsforms.com/submissions/v3/integration/secure/submit/{portalId}/{formGuid}
Esta diferencia no es una propiedad de tu cuenta. La ruta simple no requiere credenciales; la ruta requiere un token de acceso de aplicación privada con el alcance , que HubSpot documenta actualmente. Elige la ruta segura a menos que tengas una razón para no hacerlo, y configura el token en consecuencia en lugar de tratarlo como opcional.secureforms
La URL elegida se convierte en y el token en . Utilizo la estructura del cuerpo de la solicitud de la referencia de la API de formularios de HubSpot, y el manejador mantiene esa estructura en una sola función, por lo que cualquier cambio se hace en un solo lugar.HUBSPOT_SUBMIT_URL,HUBSPOT_TOKEN
Ahora deberías tener una URL de endpoint y una lista de nombres internos de propiedades, incluyendo . Si el endpoint requiere un token, también deberías tenerlo a mano.email
2. Prepara la estructura de la aplicación Next.js para Webflow Cloud
Crea un proyecto de Next.js nuevo en la versión que admite Webflow Cloud, seleccionando el App Router y TypeScript, y deja la configuración del entorno de ejecución tal cual:
npx create-next-app@latest hubspot-bridge --typescript --eslint --app --no-src-dir
cd hubspot-bridge
npm run dev
Fija las opciones en lugar de responder a las preguntas, y acepta el directorio ; esto cambia todas las rutas de archivo en esta guía y el alias de importación junto con ellas.src/@/
Ahora tienes una aplicación funcionando en tu equipo, con un directorio listo para una ruta.app
La página dice que Webflow Cloud despliega tu aplicación usando el entorno de ejecución Edge para un alojamiento rápido y distribuido globalmente, y la misma página te indica que añadas la directiva del entorno de ejecución edge de Next.js a tus rutas de API.
La palabra "edge" significa dos cosas distintas en esos dos contextos. La primera se refiere a Cloudflare Workers, la plataforma donde se ejecuta tu aplicación. La segunda se refiere al objetivo del entorno de ejecución edge de Next.js, un modo de compilación independiente que el adaptador de OpenNext para Cloudflare no admite.
Añadir genera una compilación que no funciona. Tu aplicación ya está en una plataforma edge sin necesidad de eso. No incluyas la directiva en ningún archivo y tendrás una estructura lista para desplegar.export const runtime = 'edge'
3. Escribe el manejador de rutas que redirige a HubSpot
Crea una ruta pública que filtre las solicitudes antes de que lleguen a HubSpot. El ejemplo verifica el origen y filtra bots con un "honeypot". También mapea los campos de Webflow y añade un token de portador opcional. El manejador no importa nada más que y no utiliza módulos integrados de Node.next/server
Esto evita problemas de compatibilidad de módulos específicos con Workers. La verificación del origen es importante porque, sin ella, cualquiera que encuentre la ruta puede enviar basura a tu CRM usando tus credenciales. Un navegador establece esa cabecera por ti; cualquier cosa que acceda a la ruta desde una terminal puede establecerla como quiera.
Considera la lista de permitidos como un filtro para basura ocasional. No autentica a los clientes.
Advertencia de seguridad: Este ejemplo no tiene autenticación de entrada, límite de tasa, límite de solicitud o tamaño de cuerpo, ni límite de escritura en el CRM. La lista de permitidos de origen y el "honeypot" son solo filtros y pueden ser evitados por clientes directos.
Antes de usar la ruta en producción, añade límites de tasa a través de compatibles y aplica límites de tamaño de solicitud y de escritura en el CRM adecuados para tu despliegue.middleware.ts
Crea el archivo en app/api/hubspot/route.ts con este contenido:
import { NextResponse } from 'next/server';
type Incoming = {
fields?: Record<string, string>;
pageUri?: string;
pageName?: string;
};
function allowedOrigin(origin: string): boolean {
const allowed = (process.env.ALLOWED_ORIGINS ?? '')
.split(',')
.map((o) => o.trim())
.filter(Boolean);
return allowed.length === 0 || allowed.includes(origin);
}
// Keep HubSpot's body shape in one place.
// Only send fields the HubSpot form actually defines: an unknown
// field makes HubSpot reject the whole submission with a 400 and
// FIELD_NOT_IN_FORM_DEFINITION, so forwarding the form wholesale is
// how this breaks in production.
const HUBSPOT_FIELDS = ['firstname', 'lastname', 'email', 'message'] as const;
function toHubSpotPayload(body: Incoming, hutk?: string) {
const submitted = body.fields ?? {};
const fields = HUBSPOT_FIELDS
.filter((name) => submitted[name] !== undefined && submitted[name] !== '')
.map((name) => ({ name, value: submitted[name] }));
return {
fields,
context: {
// hutk ties the submission to the visitor's HubSpot tracking
// session and is what lets HubSpot deduplicate the contact.
...(hutk ? { hutk } : {}),
pageUri: body.pageUri,
pageName: body.pageName,
},
};
}
export async function POST(request: Request) {
const origin = request.headers.get('origin') ?? '';
if (!allowedOrigin(origin)) {
return NextResponse.json({ ok: false, error: 'origin not allowed' }, { status: 403 });
}
let body: Incoming;
try {
body = (await request.json()) as Incoming;
} catch {
return NextResponse.json({ ok: false, error: 'invalid json' }, { status: 400 });
}
// Honeypot: real visitors never fill the hidden "website" field.
if (body.fields?.website) {
return NextResponse.json({ ok: true });
}
if (!body.fields?.email) {
return NextResponse.json({ ok: false, error: 'email required' }, { status: 400 });
}
const url = process.env.HUBSPOT_SUBMIT_URL;
if (!url) {
return NextResponse.json({ ok: false, error: 'HUBSPOT_SUBMIT_URL not set' }, { status: 500 });
}
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (process.env.HUBSPOT_TOKEN) {
headers.Authorization = `Bearer ${process.env.HUBSPOT_TOKEN}`;
}
const upstream = await fetch(url, {
method: 'POST',
headers,
// The route is same-origin with the page, so the browser sends
// the HubSpot tracking cookie along with the POST.
body: JSON.stringify(
toHubSpotPayload(
body,
request.headers
.get('cookie')
?.match(/(?:^|;\s*)hubspotutk=([^;]+)/)?.[1],
),
),
});
if (!upstream.ok) {
console.error('HubSpot rejected submission', upstream.status, await upstream.text());
return NextResponse.json({ ok: false, error: 'upstream rejected' }, { status: 502 });
}
return NextResponse.json({ ok: true });
}
El controlador acepta un cuerpo JSON desde tu propio sitio. Devuelve 200 a los bots para que no aprendan nada. Cuando HubSpot rechaza la solicitud, el controlador devuelve 502 y registra el motivo en el console. El honeypot devuelve éxito a propósito; un 400 le indica al autor del bot qué campo activó el filtro.
Reinicia npm run dev y envía un cuerpo de prueba mediante POST con curl a http://localhost:3000/api/hubspot; sin variables de entorno configuradas aún, deberías ver el 500 nombrando a HUBSPOT_SUBMIT_URL, lo que confirma que la ruta está conectada.
4. Configura las variables de entorno de HubSpot en Webflow Cloud
Establece la credencial de HubSpot en el entorno de Cloud. La documentación de variables de entorno de Webflow indica que "tanto las variables de entorno secretas como las no secretas están disponibles para el proceso de compilación de tu aplicación (por ejemplo, para la configuración del marco de autenticación) y permanecen disponibles para la aplicación desplegada en tiempo de ejecución". Los valores secretos se ocultan en los registros de compilación.
Para el trabajo local, coloca los mismos nombres en .env.local, que Next.js lee bajo npm run dev y que se mantiene fuera de git por defecto.
Configura estos cuatro nombres de variables de entorno para el despliegue:
- HUBSPOT_SUBMIT_URL: El endpoint de envío que HubSpot te proporcionó para este formulario. Es específico para cada formulario, por lo que un segundo formulario requiere una segunda variable o una segunda ruta.
- HUBSPOT_TOKEN: Configura este secreto opcional solo si tu endpoint de HubSpot requiere un token de aplicación privada; el controlador añadirá entonces una cabecera de autorización (bearer).
- ALLOWED_ORIGINS: Una lista separada por comas de los orígenes exactos desde los que se sirven tus páginas de Webflow, como
https://www.example.com. Dejarlo vacío desactiva la comprobación, lo cual solo es aceptable en entornos locales. - NEXT_PUBLIC_BASE_PATH: Configúralo como la ruta de montaje para cualquier página dentro de la aplicación que obtenga sus propias rutas. La plataforma inyecta la ruta base en el momento de la compilación y no reescribe las peticiones del cliente.
Ahora tienes configurados el endpoint, la credencial opcional, la lista de permitidos de origen y la ruta base del lado del cliente para el despliegue. Después de guardar, el entorno enumera los cuatro nombres, el valor del token se oculta y un nuevo despliegue los toma en cuenta durante la compilación y la ejecución.
5. Apunta el formulario de Webflow a la ruta montada
Añade un selector de formulario estable y nombres de entrada que coincidan con HubSpot. Si tu configuración de Webflow admite atributos personalizados, asegúrate de que la etiqueta form renderizada tenga un atributo personalizado llamado data-hubspot con el valor true.
Si no es así, adapta el selector del script a un gancho que admita tu configuración. Establece el nombre de cada entrada al nombre de la propiedad interna que registraste en HubSpot; la entrada de correo electrónico debe llamarse email.
Incluye una entrada de texto oculta adicional llamada website; ese es el honeypot que verifica el controlador. Si hay atributos personalizados disponibles, asegúrate de que el elemento que muestra tu mensaje de error tenga el atributo personalizado data-hubspot-error, ya que el script busca ese atributo cuando falla un envío. De lo contrario, adapta la búsqueda de errores a un gancho que admita tu configuración.
Si utilizas el enfoque de atributos personalizados, confirma que el atributo esté presente en el elemento Formulario renderizado. Con el atributo configurado en el formulario, el script de la página podrá encontrarlo.
Luego, añade este script a través de un mecanismo de código personalizado a nivel de página disponible en tu configuración de Webflow:
<script>
(function () {
var MOUNT_PATH = '/forms'; // your Webflow Cloud mount path
var form = document.querySelector('form[data-hubspot="true"]');
if (!form) return;
form.addEventListener('submit', function (event) {
event.preventDefault();
event.stopImmediatePropagation();
var fields = {};
new FormData(form).forEach(function (value, key) {
fields[key] = String(value);
});
fetch(MOUNT_PATH + '/api/hubspot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fields: fields,
pageUri: window.location.href,
pageName: document.title
})
})
.then(function (res) {
if (!res.ok) throw new Error('submit failed');
window.location.assign('/thank-you');
})
.catch(function () {
var fail = form.parentElement.querySelector('[data-hubspot-error]');
if (fail) fail.style.display = 'block';
});
}, true);
})();
</script>
El oyente se registra en la fase de captura y llama a stopImmediatePropagation, para que se ejecute antes del manejo de envío predeterminado de la página y lo detenga. MOUNT_PATH debe anteponerse a la ruta: la página de "bring-your-own-app" indica que "las llamadas fetch del lado del cliente deben incluir manualmente la ruta base", y una petición a /api/hubspot sin el prefijo llega al sitio de Webflow y no encuentra tu aplicación.
Reemplaza /thank-you con tu página de éxito real. Publica el sitio y el formulario debería interceptar su propio envío; al abrir la pestaña de Red del navegador y enviar, verás un POST a /forms/api/hubspot y evitarás que la página se recargue.
6. Despliega la aplicación y prueba el envío de principio a fin
Envía la aplicación, despliégala en el entorno de Webflow Cloud donde configuraste las variables y confirma que esté montada en la ruta que utiliza el script. Prueba la ruta antes de tocar el formulario. Una solicitud directa a la ruta montada aísla los fallos de la aplicación de los fallos del formulario.
Envía una solicitud que imite al script, con el encabezado Origin que espera tu lista de permitidos:
curl -i -X POST https://www.example.com/forms/api/hubspot \
-H "Content-Type: application/json" \
-H "Origin: https://www.example.com" \
-d '{"fields":{"email":"test@example.com"},"pageUri":"https://www.example.com/contact","pageName":"Contact"}'
Un 200 con {"ok":true} significa que el controlador llegó a HubSpot y HubSpot aceptó el cuerpo; un 502 significa que el controlador se ejecutó, pero HubSpot lo rechazó y la línea console.error emite el estado y el texto de respuesta de HubSpot para esa solicitud. Ahora envía el formulario real en la página publicada y busca el contacto en HubSpot.
También me gusta enviar una vez con el honeypot relleno a través de la función de editar y reenviar de la pestaña Red para confirmar que el devuelve un resultado sin crear nada en HubSpot. Cuando ambas comprobaciones pasan, tienes un formulario que publica en tu CRM a través de código que tú controlas, sin ninguna cuenta de automatización de por medio.200
¿Qué causa que un envío de formulario de Webflow a HubSpot falle?
Los fallos suelen provenir de una directiva de tiempo de ejecución no compatible, un prefijo de ruta montada faltante, variables de entorno de producción que no coinciden, o un selector y oyente de formulario que nunca interceptan el envío. Estos problemas pueden aparecer como compilaciones fallidas, errores 404 en vivo, errores de autorización o recargas de página.
Relaciona el síntoma visible con la causa y solución a continuación.
La compilación de Webflow Cloud falla mientras que npm run build tiene éxito localmente
Causa: El repositorio puede contener export const runtime = 'edge' en un controlador de ruta u otro archivo copiado. Webflow Cloud despliega la aplicación en Cloudflare Workers, pero la directiva de tiempo de ejecución edge de Next.js selecciona un destino de compilación independiente que el adaptador de OpenNext para Cloudflare no admite.
Un andamio o fragmento copiado puede llevar la directiva a un archivo que no inspeccionaste, lo que explica por qué comprobar solo el controlador de HubSpot puede hacer que se te pase por alto.
Solución: Busca en todo el repositorio export const runtime = 'edge' en lugar de inspeccionar una sola ruta. Elimina cada aparición de la directiva, deja el resto de la configuración de tiempo de ejecución como está y vuelve a desplegar la aplicación en Webflow Cloud. La aplicación permanece en la plataforma edge de Workers sin esa exportación.
Una compilación en la nube exitosa y una ruta montada accesible confirman que el destino de tiempo de ejecución de Next.js no compatible ya no forma parte del despliegue.
Los envíos devuelven 404 en el sitio en vivo pero 200 bajo npm run dev
Causa: Es probable que a la solicitud en vivo le falte la ruta de montaje de Webflow Cloud. El desarrollo local sirve el controlador directamente en /api/hubspot, mientras que la aplicación desplegada reside debajo de la ruta seleccionada, como /forms. Webflow Cloud no reescribe las llamadas fetch del lado del cliente para incluir esa ruta base.
Como resultado, una solicitud fetch en vivo a /api/hubspot llega al sitio de Webflow en lugar de a la aplicación montada y devuelve 404.
Solución: Inspecciona la URL POST en la pestaña Red del navegador y confirma que comienza con la ruta de montaje configurada. Establece MOUNT_PATH en ese prefijo exacto, para que la solicitud de ejemplo se convierta en /forms/api/hubspot, y luego vuelve a publicar el sitio de Webflow.
Para las páginas dentro de la aplicación que obtienen sus propias rutas, usa NEXT_PUBLIC_BASE_PATH. Un 200 en vivo desde la URL con prefijo confirma que el navegador está llegando al controlador de ruta desplegado en lugar de a la capa de página de Webflow.
HubSpot devuelve 401 o 403 en producción, pero el mismo curl tiene éxito desde tu portátil
Causa: Un 401 o 403 que solo ocurre en vivo apunta a una discrepancia en el entorno. El token o el punto final pueden existir solo en .env.local, pertenecer a un entorno de Webflow Cloud diferente o estar vacíos en el entorno desplegado.
El desarrollo local de Next.js lee .env.local, pero el controlador desplegado lee los valores configurados para su entorno de Webflow Cloud. Un HUBSPOT_TOKEN vacío también provoca que el controlador no envíe ningún encabezado de autorización.
Solución: Compara los valores desplegados de HUBSPOT_SUBMIT_URL y HUBSPOT_TOKEN con los nombres locales, corrige cualquier valor faltante o que no coincida, y vuelve a desplegar para que la compilación y el tiempo de ejecución los reciban. Si la misma prueba con curl sigue fallando, inspecciona el estado y el cuerpo de la respuesta emitidos por console.error.
Un 401 persistente significa que el valor del token es incorrecto o ha caducado, así que renuévalo en HubSpot y actualiza el secreto en el entorno de Webflow Cloud correspondiente.
La página se recarga al enviar y nada llega a la ruta
Causa: El script del navegador no puede encontrar el formulario o no puede tomar el control de su evento de envío. Un atributo data-hubspot faltante hace que document.querySelector('form[data-hubspot="true"]') no devuelva nada y provoca que el script se detenga.
Un script que no se carga en la página publicada, o un detector modificado desde la configuración de la fase de captura, también pueden dejar el control del envío predeterminado de la página y provocar una recarga.
Solución: Ejecuta el selector en la consola del navegador y confirma que devuelve el formulario esperado. Verifica que la página incluya el script, que el detector esté registrado con true para la fase de captura y que siga llamando a preventDefault y stopImmediatePropagation.
Vuelve a publicar el sitio y realiza una actualización forzada de la página para cargar el código actual. Un POST a la ruta montada en la pestaña Red, sin que la página se recargue inmediatamente, confirma que el detector interceptó el envío.
Qué puedes construir a continuación con HubSpot y Webflow
Los especialistas en marketing editan la página visualmente en Webflow mientras tu equipo despliega el controlador de rutas en Webflow Cloud y mantiene la credencial en un solo entorno.
El mismo patrón de controlador se extiende a cualquier cosa que HubSpot acepte a través de HTTP. El CMS de Webflow ahora cuenta con API REST ampliadas, por lo que la aplicación que reenvía un envío también puede leer el contenido del CMS y pasar el contexto relevante a HubSpot junto con los campos.
Para una personalización más profunda más allá de lo que un solo controlador de rutas maneja de forma nativa, la documentación para desarrolladores de Webflow cubre el tiempo de ejecución de Webflow Cloud y las API del CMS.
Preguntas frecuentes
¿Puedo añadir un middleware para limitar la tasa de peticiones o bloquear la ruta?
Sí, puedes, con un archivo llamado middleware.ts. La documentación de personalización del framework de Webflow indica que Webflow Cloud admite middleware de tiempo de ejecución Edge en el entorno de Workers; el middleware de tiempo de ejecución de Node.js no puede ejecutarse allí. Las versiones más recientes de Next.js renombran el middleware a proxy, pero proxy se ejecuta en el entorno de Node y no puede optar por Edge, por lo que un proxy.ts renombrado no puede ejecutarse en Webflow Cloud.
¿Funciona node:fs para escribir envíos en un archivo de registro?
No puedes escribir envíos en un archivo de registro con node:fs en Webflow Cloud. node:fs requiere una fecha de compatibilidad de Workers más reciente que la que utiliza actualmente Webflow Cloud. Puedes usar node:path si necesitas manejar rutas. Para un registro duradero, puedes reenviar una copia de cada envío a un almacén que admita HTTP.
¿Puedo usar Astro o Vite con Webflow Cloud?
Sí, puedes usar Astro o Vite con Webflow Cloud. Webflow Cloud admite Next.js 15 o superior, Astro 6 o 7, y Vite 6.1 o superior con React, Vue, Svelte o JavaScript básico. Todos ellos requieren Node.js 22 o posterior a nivel local, y npm es actualmente el único gestor de paquetes compatible. La lógica del controlador sigue siendo la misma: verifica el origen y luego reenvía el cuerpo mapeado a HubSpot. Solo la ubicación del archivo y la firma de exportación cambian según el framework.
¿Puedo verificar una solicitud firmada con timingSafeEqual en esta ruta?
En la aplicación desplegada, sí: timingSafeEqual existe en el entorno de ejecución de Workers. No está disponible en el next dev local, por lo que una comparación que funciona en producción fallará en tu portátil. Si añades una comprobación de firma, coloca la comparación detrás de un pequeño asistente y prueba la ruta desplegada con curl en lugar de confiar en los resultados locales.





