Puedes conectar la página de campaña de Webflow de cada cliente a HubSpot una sola vez y, después, lanzar la siguiente con un elemento de CMS y un clic en publicar.
Dos páginas de campaña de Webflow pueden tener el mismo formulario, el mismo portal de HubSpot y los mismos nombres de campo, y aun así registrar datos distintos en el CRM. Cada página se conectó a HubSpot mediante el método de integración más cómodo en el momento del lanzamiento.
Webflow añade prefijos a los ID de formulario generados con wf-form-, lo cual es suficiente para que el rastreador de formularios de HubSpot vea un mismo formulario como dos distintos y registre cada envío por duplicado. Los parámetros UTM que estaban en la página de destino del anuncio desaparecen cuando el visitante llega a la página del formulario, por lo que la inversión en publicidad social no se atribuye a nada. Ninguno de estos problemas aparece como un error.
El formulario estándar de HubSpot se renderiza dentro de un iframe, y los scripts de la página principal no pueden acceder a él. Este hecho determina la arquitectura, ya que provoca tanto que los estilos se sobrescriban un segundo después de publicar como que los valores UTM nunca se rellenen en los campos ocultos.
Utilizar la API de formularios desde un gestor de rutas de Webflow Cloud evita ambos problemas: el formulario sigue siendo un formulario nativo de Webflow al que puedes aplicar estilos como a cualquier otro elemento, y el contexto de la campaña se ensambla en el servidor.
Por lo tanto, lo que merece la pena construir no es otra integración. Es una ruta de envío única que compartan todos los formularios de campaña en los sitios de tus clientes, donde lanzar la undécima campaña sea solo un elemento de CMS, una invocación de script y un clic en publicar.
¿Qué necesitas para crear flujos de trabajo escalables para campañas de clientes en Webflow con HubSpot?
Necesitas un sitio de Webflow con el CMS disponible y una aplicación de Webflow Cloud desplegada en él; un token de aplicación privada de HubSpot con permisos de formulario y CRM; el código de seguimiento de HubSpot activo en el sitio del cliente; y una colección que contenga la configuración de cada campaña.
Solo el último punto es específico para cada campaña. El token y el código de seguimiento son una configuración única por portal de HubSpot, que es lo que hace que el segundo cliente sea más rápido de configurar que el primero.
Aquí tienes el aspecto que debe tener cada pieza antes de ejecutar cualquier código.
Un sitio de Webflow con CMS y una aplicación de Webflow Cloud
El CMS es lo que hace que esto sea repetible, y no está incluido en todos los planes. La oferta de Webflow cambió en mayo de 2026: los antiguos planes CMS y Business se fusionaron en un único plan Premium Site, desde 25 dólares al mes con facturación anual, que incluye 20 000 elementos de CMS y 40 colecciones.
El plan Basic no tiene CMS, por lo que el plan Premium es el requisito mínimo práctico. Montar una aplicación de Webflow Cloud en un dominio personalizado tampoco está disponible en los planes Starter y Basic, lo cual es importante aquí porque la cookie de seguimiento y la página de campaña deben compartir el mismo origen. Las cifras actuales están en la página de planes y precios de Webflow.
En cuanto a la aplicación, necesitas Next.js 15 o superior, Node.js 22 o superior en local y npm. Webflow Cloud solo admite npm actualmente, por lo que si un proyecto usa pnpm o Yarn, deberá regenerar su archivo de bloqueo (lockfile) antes del primer despliegue.
Un token de aplicación privada de HubSpot con los permisos adecuados
Las llamadas del lado del servidor requieren un token, que proviene de una aplicación privada. HubSpot ahora los lista en Desarrollo, luego en Aplicaciones heredadas (Legacy apps), donde haces clic en Crear aplicación heredada, eliges Privada, le pones nombre en la pestaña de Información básica y añades los permisos en la pestaña de Permisos (Scopes).
Debes ser superadministrador, y el token aparecerá en la pestaña de Autenticación tras hacer clic en Mostrar token.
Cuatro permisos cubren toda la implementación:
formspara los endpoints de envíocrm.objects.contacts.writeycrm.objects.contacts.readpara insertar contactos directamentecrm.schemas.contacts.readpara que puedas confirmar que una propiedad personalizada existe antes de escribir en ella
El registro de campañas también requiere marketing.campaigns.read y marketing.campaigns.write, ambos restringidos a Marketing Hub Professional y Enterprise.
Una precaución que vale la pena tener en cuenta al diseñar: HubSpot vincula el token al usuario que lo creó, y la documentación indica que eliminar a ese usuario provoca que las llamadas fallen con USER_DOES_NOT_HAVE_PERMISSIONS. Por esa misma razón, creo las aplicaciones privadas de los clientes bajo una identidad de administrador compartida.
Código de seguimiento de HubSpot instalado en el sitio del cliente
El código de seguimiento establece la cookie hubspotutk y, sin ella, los envíos llegan sin historial de visitas. Cópialo desde HubSpot en Seguimiento y analítica, luego en Código de seguimiento, dentro de la sección Código de inserción.
La instrucción de HubSpot es pegarlo antes de la etiqueta de cierre </body> , lo que en Webflow significa ir a Configuración del sitio, luego a la pestaña Código personalizado, y colocarlo en el Código del pie de página en lugar del Código del encabezado. Cada campo admite hasta 50,000 caracteres y nada surte efecto hasta que se publica el sitio.
Instala solo uno. HubSpot es explícito: "si hay varios códigos de seguimiento de HubSpot instalados, se activará el primero que se cargue en la página", un riesgo real en un sitio que ejecuta tanto la aplicación de HubSpot como un fragmento de código pegado manualmente.
La cookie contiene un GUID de visitante opaco y caduca en seis meses. En toda una cartera de proyectos, insertar el fragmento mediante programación es mejor que pegarlo sitio por sitio, y la guía cubre la API de código personalizado.
Una colección de campañas que contiene la configuración por campaña
Esto es lo que convierte una integración puntual en un flujo de trabajo. Crea una colección desde la pestaña CMS llamada Campañas, con los campos que el controlador necesita para dirigir un envío: texto plano para el GUID del formulario de HubSpot, texto plano para el ID del portal, texto plano para el valor UTM de la campaña y un interruptor para indicar si la campaña acepta envíos.
Dos campos hacen el trabajo pesado. El GUID del formulario le indica al controlador a qué formulario de HubSpot enviar los datos, por lo que un solo controlador puede atender a todos los clientes. El interruptor de activo permite que el equipo de marketing cierre una campaña sin pedirte que realices un despliegue.
Mantén los GUID como texto plano en lugar de texto enriquecido, porque el texto enriquecido llega envuelto en HTML cuando lo lees a través de la API.
7 pasos para construir un flujo de trabajo de campañas escalable de Webflow Cloud a HubSpot
En HubSpot, creas las propiedades personalizadas que llevan el contexto de la campaña al registro del contacto. En Webflow, construyes una plantilla de página de colección que renderiza cada campaña y lleva sus identificadores de HubSpot como atributos vinculados al CMS. En tu aplicación de Webflow Cloud, construyes el asistente de la API de formularios, el controlador de rutas y el script del cliente que los conecta.
Síguelos en orden, ya que cada paso consume un valor producido por el anterior.
1. Añade las credenciales de HubSpot como variables de entorno de Webflow Cloud
El token de la aplicación privada nunca debe llegar al navegador, así que colócalo en variables de entorno en lugar de en un archivo comprometido. Abre el entorno de tu aplicación en el panel de control de Webflow Cloud, busca la sección Variables de entorno y usa Añadir variable, luego Añadir variable única, para cada valor.
Si ya los tienes en un .env local, la importación masiva toma todo el archivo de una vez, que es como configuro cada nuevo entorno de cliente:
HUBSPOT_PRIVATE_APP_TOKEN=pat-na1-00000000-0000-0000-0000-000000000000
HUBSPOT_PORTAL_ID=12345678
NEXT_PUBLIC_BASE_PATH=/campaigns
Márcalo HUBSPOT_PRIVATE_APP_TOKEN como secreto, lo que le indica a Webflow Cloud que lo oculte en el panel de control. HUBSPOT_PORTAL_ID es tu ID de Hub, que puedes encontrar haciendo clic en el nombre de tu cuenta en la esquina superior derecha de HubSpot y leyendo el valor que aparece en Cuenta.
NEXT_PUBLIC_BASE_PATH debe coincidir con la ruta de montaje de la aplicación y lleva el prefijo público a propósito, ya que el script del navegador lo necesita para construir la URL de obtención. Una ruta de montaje no es un secreto.
Las variables llegan tanto al proceso de compilación como a la aplicación desplegada en tiempo de ejecución, pero no afectan a un despliegue que ya existe. Envía un commit después de cualquier cambio. Un token añadido después del último despliegue devuelve un error 401 sin ninguna otra señal, lo cual resulta confuso porque el valor parece correcto en el panel de control.
2. Crea las propiedades personalizadas de HubSpot a las que escribe el flujo de trabajo
HubSpot no tiene propiedades predeterminadas para los valores UTM sin procesar ni para identificar a qué cliente de agencia pertenece un contacto, así que créalas antes del primer envío.
Hacerlo a través de la API significa que las mismas propiedades existen de forma idéntica en cada portal que incorpores, lo cual marca la diferencia entre un flujo de trabajo y un conjunto de configuraciones similares:
// scripts/create-hubspot-properties.mjs
// Run once per HubSpot portal.
const TOKEN = process.env.HUBSPOT_PRIVATE_APP_TOKEN
const properties = [
{ name: 'campaign_slug', label: 'Campaign slug' },
{ name: 'agency_client', label: 'Agency client' },
{ name: 'utm_source_raw', label: 'UTM source (raw)' },
{ name: 'utm_medium_raw', label: 'UTM medium (raw)' },
]
for (const property of properties) {
const response = await fetch(
'https://api.hubapi.com/crm/properties/2026-03/contacts',
{
method: 'POST',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
groupName: 'contactinformation',
name: property.name,
label: property.label,
type: 'string',
fieldType: 'text',
}),
}
)
console.log(property.name, response.status, await response.text())
}
El name valor es el nombre interno, y cada llamada posterior a la API hace referencia a él. HubSpot indica que no puedes editar el nombre interno de una propiedad después de crearla, por lo que vale la pena seguir la convención de minúsculas con guiones bajos en el script, aunque HubSpot la documente como una convención y no como una regla.
El par type y fieldType establece el comportamiento: string con text genera un campo de texto de una sola línea. Reutilizar contactinformation como groupName coloca las propiedades donde el equipo de marketing las buscará.
Ejecútalo una vez por portal y lee los códigos de estado, porque una propiedad que ya existe devuelve un error en lugar de completarse silenciosamente. Eso es justo lo que quieres al incorporar a un cliente cuyo portal fue configurado por otra persona.
3. Crea la plantilla de la página de campaña en una página de Colección CMS
Abre la página de la Colección de Campañas y construye el diseño una sola vez: encabezado, texto de la oferta y un bloque de formulario nativo. Vincula los elementos de texto a sus campos de Colección como de costumbre. Lo que convierte esto en un flujo de trabajo son los atributos personalizados del contenedor del formulario.
Selecciona el contenedor del formulario, abre el panel de configuración de elementos, busca Atributos personalizados y haz clic en el icono de más. Añade data-hs-form-id, luego haz clic en el punto morado en el campo Valor y conéctalo al campo GUID del formulario de HubSpot.
Repite para data-hs-portal-id, data-campaign-slug, y data-agency-client. Webflow solo permite datos de CMS en atributos personalizados en páginas de Colección o dentro de listas de Colección, que es donde reside este formulario:
<!-- Rendered output for one Collection item -->
<div class="campaign-form-wrapper"
data-hs-form-id="1a2b3c4d-5e6f-7890-abcd-ef1234567890"
data-hs-portal-id="12345678"
data-campaign-slug="q3-demand-gen-webinar"
data-agency-client="northwind-labs">
<form id="wf-form-Campaign-Form" data-name="Campaign Form">
<input type="email" name="email" data-hs-property="email" required>
<input type="text" name="firstname" data-hs-property="firstname">
<select name="company_size" data-hs-property="company_size">...</select>
<input type="submit" value="Get the guide">
</form>
</div>
Lee ese marcado como una tabla de enrutamiento, no como un formulario. Los cuatro atributos del contenedor le indican al script a qué portal y formulario pertenece el envío, qué contexto de campaña adjuntar y qué valor proviene del elemento de la Colección.
El data-hs-property atributo en cada entrada es la segunda mitad del diseño: nombra la propiedad interna de HubSpot a la que se asigna el campo, por lo que un campo nuevo es solo un atributo en lugar de una asignación configurada en un panel de control. El select elemento se coloca ahí sin más, porque la API de Formularios acepta cualquier tipo de propiedad.
Para reutilizar el diseño en sitios de clientes, convierte las secciones en componentes usando el método de la guía de diseño de sistemas para páginas de destino.
Una limitación es que la función de Bibliotecas de Webflow, que comparte componentes entre sitios en un Espacio de trabajo, está en fase beta, y los componentes que contienen una lista de Colección se ocultan al compartirlos, por lo que una biblioteca compartida conserva el estilo de tu formulario pero no sus enlaces de CMS.
4. Crea el asistente de la API de Formularios de HubSpot
Con la página emitiendo sus propios datos de enrutamiento, la aplicación necesita una función que convierta un envío en una llamada a la API de Formularios. Crea lib/hubspot.ts.
La estructura de la solicitud proviene del punto de conexión de envío autenticado de HubSpot, que toma el ID del portal y el GUID del formulario en la ruta y el token en el encabezado Bearer:
// lib/hubspot.ts
export type HubSpotContext = {
hutk?: string
ipAddress?: string
pageUri?: string
pageName?: string
}
export type SubmissionResult = {
ok: boolean
status: number
redirectUri?: string
errorType?: string
}
const CONTACT_OBJECT_TYPE_ID = '0-1'
function toFields(values: Record<string, string>) {
return Object.entries(values)
.filter(([, value]) => value !== '' && value != null)
.map(([name, value]) => ({
objectTypeId: CONTACT_OBJECT_TYPE_ID,
name,
value: String(value),
}))
}
export async function submitToHubSpotForm(args: {
portalId: string
formGuid: string
values: Record<string, string>
context: HubSpotContext
consentText?: string
}): Promise<SubmissionResult> {
const token = process.env.HUBSPOT_PRIVATE_APP_TOKEN
if (!token) throw new Error('Missing HUBSPOT_PRIVATE_APP_TOKEN')
const body: Record<string, unknown> = {
submittedAt: String(Date.now()),
fields: toFields(args.values),
context: args.context,
}
if (args.consentText) {
body.legalConsentOptions = {
consent: { consentToProcess: true, text: args.consentText },
}
}
const response = await fetch(
`https://api.hsforms.com/submissions/v3/integration/secure/submit/` +
`${args.portalId}/${args.formGuid}`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}
)
const payload = (await response.json().catch(() => ({}))) as {
redirectUri?: string
errors?: { errorType?: string }[]
}
return {
ok: response.ok,
status: response.status,
redirectUri: payload.redirectUri,
errorType: payload.errors?.[0]?.errorType,
}
}
Estos tres detalles hacen que los envíos fallen si no son correctos. Cada entrada en fields lleva objectTypeId, que es requerido por el esquema de HubSpot y que los tutoriales antiguos omiten porque no siempre se aplicaba; 0-1 es el objeto de contacto.
submittedAt es una marca de tiempo en milisegundos, y HubSpot rechaza los valores con más de un mes de antigüedad, así que genérala en el momento del envío en lugar de arrastrarla a través de una cola.
Y la URL y el título de la página deben ir en context como pageUri y pageName, nunca dentro de fields, porque un formulario que no los definió como campos rechaza todo el envío con FIELD_NOT_IN_FORM_DEFINITION.
Hago que el asistente devuelva un resultado en lugar de lanzar un error, porque un 400 de HubSpot es un problema de datos que vale la pena registrar en la campaña, no una excepción que deba tumbar al gestor. Leer el token dentro del cuerpo de la función también es importante: en Webflow Cloud trato cada lectura de process.env como algo con alcance de solicitud en lugar de almacenarlo en caché a nivel de módulo.
5. Crea el gestor de rutas para los leads de la campaña
Este es el único endpoint al que se conecta cada formulario de campaña en cada sitio del cliente. Lee los datos de enrutamiento que envía el navegador, añade el contexto que solo el servidor puede proporcionar y llama al asistente.
Crea :app/api/campaign-lead/route.ts
// app/api/campaign-lead/route.ts
export const runtime = 'edge'
import { NextResponse, type NextRequest } from 'next/server'
import { submitToHubSpotForm } from '@/lib/hubspot'
type LeadPayload = {
portalId: string
formGuid: string
campaignSlug: string
agencyClient: string
values: Record<string, string>
hutk?: string
pageUri?: string
pageName?: string
utm?: { source?: string; medium?: string }
consentText?: string
}
function clientIp(request: NextRequest): string | undefined {
// Cloudflare populates cf-connecting-ip and recommends it over
// x-forwarded-for. Webflow Cloud does not document request headers,
// so treat this as best effort and never require a value.
return (
request.headers.get('cf-connecting-ip') ??
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??
undefined
)
}
export async function POST(request: NextRequest) {
let payload: LeadPayload
try {
payload = (await request.json()) as LeadPayload
} catch {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
}
if (!payload.portalId || !payload.formGuid) {
return NextResponse.json({ error: 'Missing routing data' }, { status: 400 })
}
if (!payload.values?.email) {
return NextResponse.json({ error: 'Email is required' }, { status: 422 })
}
const result = await submitToHubSpotForm({
portalId: payload.portalId,
formGuid: payload.formGuid,
consentText: payload.consentText,
values: {
...payload.values,
campaign_slug: payload.campaignSlug,
agency_client: payload.agencyClient,
utm_source_raw: payload.utm?.source ?? '',
utm_medium_raw: payload.utm?.medium ?? '',
},
context: {
hutk: payload.hutk,
ipAddress: clientIp(request),
pageUri: payload.pageUri,
pageName: payload.pageName,
},
})
if (!result.ok) {
console.error('HubSpot rejected submission', {
campaign: payload.campaignSlug,
status: result.status,
errorType: result.errorType,
})
return NextResponse.json(
{ error: result.errorType ?? 'Submission failed' },
{ status: result.status === 429 ? 429 : 502 }
)
}
return NextResponse.json({ ok: true, redirectUri: result.redirectUri })
}
La línea export const runtime = 'edge' no es opcional. Webflow Cloud ejecuta tu aplicación en Cloudflare Workers, y la documentación de Webflow indica añadir esa directiva a las rutas de la API. El desarrollo local funciona sin ella, pero la producción falla, lo cual es difícil de diagnosticar porque nada en tu salida de desarrollo da pistas al respecto.
El asistente clientIp se gana su comentario. El esquema de HubSpot documenta context.ipAddress como la IP del visitante, y los envíos que llegan sin ella llevan una advertencia de HubSpot sobre las analíticas de formularios.
Cloudflare documenta CF-Connecting-IP como la cabecera que lleva la IP del cliente y la recomienda sobre X-Forwarded-For porque contiene una única dirección.
Webflow Cloud no documenta nada sobre las cabeceras de solicitud, así que leo primero la cabecera de Cloudflare y dejo pasar un valor faltante como indefinido. Enviar la IP de tu servidor sería peor que no enviar ninguna, porque podría interpretarse como tráfico automatizado.
6. Conecta el formulario de Webflow al gestor de rutas
El script del cliente es el archivo más pequeño de la compilación y el que contiene más casos especiales. Intercepta el envío, lee lo que solo existe en el navegador, lo publica en el gestor y muestra el estado de éxito o error del propio formulario.
Añádelo como un Code Embed en la página de Colección, o en el código del pie de página si todas las páginas de la campaña lo comparten:
<script>
document.addEventListener('DOMContentLoaded', function () {
var BASE_PATH = '/campaigns' // must match NEXT_PUBLIC_BASE_PATH
function readCookie(name) {
var parts = ('; ' + document.cookie).split('; ' + name + '=')
return parts.length === 2 ? parts.pop().split(';').shift() : undefined
}
document.querySelectorAll('[data-hs-form-id]').forEach(function (wrapper) {
var form = wrapper.querySelector('form')
if (!form) return
var success = wrapper.querySelector('[data-campaign-state="success"]')
var failure = wrapper.querySelector('[data-campaign-state="error"]')
var button = form.querySelector('[type="submit"]')
form.addEventListener('submit', async function (event) {
event.preventDefault()
if (button) button.value = 'Sending...'
var values = {}
form.querySelectorAll('[data-hs-property]').forEach(function (input) {
values[input.dataset.hsProperty] =
input.type === 'checkbox' ? String(input.checked) : input.value
})
var params = new URLSearchParams(window.location.search)
try {
var response = await fetch(BASE_PATH + '/api/campaign-lead', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
portalId: wrapper.dataset.hsPortalId,
formGuid: wrapper.dataset.hsFormId,
campaignSlug: wrapper.dataset.campaignSlug,
agencyClient: wrapper.dataset.agencyClient,
values: values,
hutk: readCookie('hubspotutk'),
pageUri: window.location.href,
pageName: document.title,
utm: {
source: params.get('utm_source') || undefined,
medium: params.get('utm_medium') || undefined,
},
}),
})
if (!response.ok) throw new Error('Submission failed')
form.style.display = 'none'
if (success) success.style.display = 'block'
} catch (error) {
if (failure) failure.style.display = 'block'
if (button) button.value = 'Try again'
}
})
})
})
</script>
Se están haciendo cuatro cosas innecesariamente. readCookie('hubspotutk') extrae el token de identidad de visitante de HubSpot, una cookie de origen legible desde JavaScript, y pasarlo como hutk es lo que permite a HubSpot asociar las visitas anteriores del usuario al contacto.
Los parámetros UTM se leen de la URL en el momento del envío, porque es el único instante en que HubSpot los captura. La llamada preventDefault() detiene el envío AJAX de Webflow, por lo que los datos van a HubSpot y no se almacenan en Webflow.
Y BASE_PATH se antepone manualmente porque Webflow Cloud gestiona el enrutamiento de enlaces e imágenes, pero deja las llamadas fetch del lado del cliente en tus manos.
Para los elementos de estado, añade un atributo personalizado data-campaign-state con el valor success o error a los elementos de mensaje de éxito y error del bloque de formulario.
Webflow genera sus propias clases de estado en los formularios publicados, pero yo apunto a mi propio atributo porque esos nombres de clase generados no son un contrato documentado, y una actualización de la plataforma puede cambiarlos. Publica, envía, y el contacto debería aparecer en HubSpot en cuestión de segundos con el slug de la campaña, el cliente y los valores UTM en el registro.
7. Registra la campaña en HubSpot y asocia el formulario a ella
El flujo de trabajo ya funciona, pero los informes no. Una campaña de HubSpot es un objeto real con su propio GUID, y hasta que el formulario no se asocia a uno, los envíos no se contabilizan en el rendimiento de la campaña.
Este script realiza ambas llamadas, y lo ejecuto como parte del lanzamiento en lugar de dejar que alguien tenga que hacer clic en la interfaz.
// scripts/register-campaign.mjs
// node scripts/register-campaign.mjs "Q3 Demand Gen Webinar" q3-webinar <formId>
const TOKEN = process.env.HUBSPOT_PRIVATE_APP_TOKEN
const [name, utmValue, formId] = process.argv.slice(2)
const created = await fetch('https://api.hubapi.com/marketing/campaigns/2026-03', {
method: 'POST',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
properties: {
hs_name: name,
hs_utm: utmValue,
hs_campaign_status: 'active',
},
}),
}).then((response) => response.json())
console.log('campaign', created.id)
const associated = await fetch(
`https://api.hubapi.com/marketing/campaigns/2026-03/${created.id}/assets/FORM/${formId}`,
{ method: 'PUT', headers: { Authorization: `Bearer ${TOKEN}` } }
)
console.log('form association', associated.status)
La primera llamada crea la campaña y devuelve su GUID como . debe ser único y tener menos de 256 caracteres, contiene el valor UTM de la campaña bajo el mismo límite, y acepta , , , , o .idhs_namehs_utmhs_campaign_statusplannedin_progressactivepausedcompleted
Configurar con la cadena que usan los enlaces de tu campaña conecta el tráfico rastreado con la campaña, y confirmo que el valor llega al registro de la campaña antes de que comience cualquier gasto en medios.hs_utm
La segunda llamada es la asociación, y vale la pena memorizar su forma: un a la ruta de activos de la campaña con el tipo de activo y el ID del activo en la URL y sin cuerpo. es el tipo de activo para formularios, y desbloquea las métricas de tasa de conversión, envíos y vistas.PUTFORM
Una limitación a tener en cuenta es que añadir un activo que ya pertenece a otra campaña lo elimina de esa campaña, por lo que un formulario utilizado por dos campañas debe convertirse en dos formularios. Guarda el GUID devuelto en el elemento de la Colección.
¿Qué hace que los flujos de trabajo de campañas de Webflow Cloud a HubSpot fallen?
Los fallos más costosos son aquellos en los que el envío se realiza correctamente. Un formulario que devuelve su estado de éxito mientras los datos llegan mal, duplicados o sin atribución no aparecerá en ningún registro de errores, y el equipo de marketing lo encontrará en un informe en lugar de en un error.
Eso hace que el orden de investigación sea más importante de lo habitual, así que empieza por el que parece un error de HubSpot pero no lo es.
Cada envío aparece dos veces en HubSpot
Este es el fallo más reportado en toda la integración, y la causa son dos mecanismos de captura en un mismo formulario. Si la captura de formularios que no son de HubSpot está activada y además envías datos a través de la aplicación o la API, el script de seguimiento rastrea el formulario del DOM mientras tu integración publica los mismos datos, por lo que el contacto muestra dos envíos bajo dos formularios con nombres diferentes.
Una variante específica de Webflow es aún más molesta. Webflow añade el prefijo a los IDs de formulario generados, por lo que un formulario puede presentar dos selectores distintos al script de HubSpot: uno que coincide con el nombre del formulario y otro con el ID generado, y HubSpot los registra como dos formularios distintos que no son de HubSpot.wf-form-
En ambos casos, la solución es desactivar la captura de formularios que no son de HubSpot para el dominio. Volver a asignar un formulario en la aplicación de HubSpot también puede generar un formulario adicional que luego recibe cada envío junto con el original, así que revisa la lista de formularios después.
HubSpot rechaza los envíos con FORM_HAS_RECAPTCHA_ENABLED
Activa CAPTCHA para un formulario de HubSpot y los envíos a través de la API fallarán. HubSpot lo indica claramente: "Si se ha activado CAPTCHA en el formulario, los envíos de formularios desde la API de envío de datos para un formulario u otras integraciones de formularios no serán aceptados".
Obtienes un error 400 cuyo es , y el mensaje legible para humanos no está documentado, así que registra el tipo de error en su lugar.errorTypeFORM_HAS_RECAPTCHA_ENABLED
Eso es un verdadero problema, porque la gente activa CAPTCHA por una razón, y el spam en los formularios de campaña no es hipotético. Mueve la protección contra spam a la capa que gestiona el envío: añade reCAPTCHA en el lado de Webflow y valida el token en tu manejador de rutas antes de llamar a HubSpot, o añade un honeypot que el manejador verifique y descarte silenciosamente.
La parte de Webflow está cubierta en la guía para añadir reCAPTCHA a los formularios. De cualquier manera, la verificación debe estar en el manejador, porque un honeypot solo del lado del cliente sigue permitiendo el paso de datos.
HubSpot advierte que no se está enviando la cookie necesaria para vincular los envíos
Esta advertencia aparece en el envío mismo y significa que llegó vacío. Tres causas explican casi todo: el código de seguimiento no está activo porque el sitio no se publicó, el visitante rechazó las cookies, una extensión bloqueó el script, o tu script leyó la cookie antes de que se configurara.context.hutk
El punto clave es que no puedes arreglar esto más tarde. HubSpot no rellena retroactivamente la asociación de vistas de página para un envío que llegó sin el token, y lo mismo ocurre con la dirección IP. Así que prepárate para su ausencia: el contacto se crea y se vincula mediante el correo electrónico; solo pierdes el historial de la sesión anterior.
Registro un contador cuando hutk falta, porque la cookie es de origen y se establece en cada vista de página, así que una tasa que salta de casi cero a la mayoría de los envíos solo puede significar que el fragmento dejó de cargarse en el sitio del cliente.
Los valores UTM están vacíos en los contactos de campañas pagadas
La versión clásica no tiene nada que ver con tu código. HubSpot captura los parámetros UTM que están en la URL en el momento del envío, y el embed estándar se renderiza en un iframe al que los scripts de la página principal no pueden acceder.
Un flujo que envía tráfico a una landing page etiquetada y luego hace clic para llegar a una página de formulario con URL limpia pierde la atribución por completo, y ninguna configuración de campo oculto en el formulario incrustado lo soluciona.
Leer los parámetros en el navegador y enviarlos a tu propio manejador, como hace el paso 6, resuelve la mitad del iframe. No resuelve la mitad de las páginas múltiples. Si el formulario vive en una página diferente a la que apunta el anuncio, transfiere los parámetros añadiéndolos al enlace interno o escribiéndolos en sessionStorage al aterrizar por primera vez y leyéndolos al momento de enviar.
Confirma también que no dependes de campos ocultos con la captura de no-HubSpot de HubSpot, que no los recopila en absoluto.
El manejador de rutas funciona localmente pero devuelve un 404 en producción
Esta es la ruta de montaje, y es el error más común que veo en Webflow Cloud. Tu aplicación se sirve en una subruta, por lo que una aplicación montada en /campaigns expone su ruta en /campaigns/api/campaign-lead.
Un fetch('/api/campaign-lead') se resuelve contra la raíz del sitio, donde no hay nada escuchando, y Webflow devuelve la página 404 del sitio. Localmente, no hay ruta de montaje, por lo que la misma llamada funciona.
Webflow Cloud maneja el prefijo para la navegación del framework y las imágenes, pero las llamadas fetch del lado del cliente son explícitamente tu responsabilidad. Prefíjalas desde NEXT_PUBLIC_BASE_PATH en lugar de una cadena codificada de forma rígida.
No establezcas basePath o assetPrefix en tu configuración de Next.js para compensar, porque Webflow Cloud establece ambos en tiempo de compilación desde la ruta de montaje y sobrescribe lo que confirmas. Comprueba primero un síntoma relacionado: un 401 en lugar de un 404 suele significar que el token se añadió después del último despliegue.
Los envíos de prueba siguen asociándose al contacto equivocado
Esto solo aparece durante el control de calidad, que es cuando más daño hace a tu confianza en la compilación. Enviar varios leads de prueba desde un mismo navegador hace que HubSpot asocie cada uno a la cookie HubSpotUTK que ya está allí, así que en lugar de cinco contactos obtienes un contacto sobrescrito cinco veces.
El comportamiento documentado de HubSpot es que el correo electrónico es el identificador principal. Aun así, cuando no existe un contacto con ese correo, la cookie se utiliza para reconocer y actualizar un contacto existente, y los valores de las propiedades, incluido el propio correo, pueden ser sobrescritos.
Un envío de prueba temprano sin nombre lo empeora, porque un contacto sin nombre sigue siendo un contacto válido, y los envíos posteriores siguen coincidiendo con él. Haz las pruebas en una ventana de incógnito nueva por cada envío, usa direcciones distintas y elimina los contactos de prueba antes de la entrega.
Si un cliente necesita un contacto nuevo para cada correo electrónico nuevo desde un mismo navegador, la opción de HubSpot "Crear siempre un contacto nuevo para un correo electrónico nuevo", establecida en el formulario individual, cambia ese comportamiento.
Escala tu flujo de trabajo de Webflow Cloud a campañas de HubSpot a través de las cuentas de tus clientes
Con un contexto de campaña limpio en cada contacto, las pruebas de variantes se vuelven medibles en lugar de anecdóticas, porque los envíos de cada variante llevan las mismas propiedades y aterrizan en la misma campaña. La metodología en nuestra guía de pruebas A/B se combina directamente con el slug de campaña que ya estás escribiendo.
La entrega es lo otro que debes planificar, porque un flujo de trabajo que solo tú puedes operar es un cuello de botella disfrazado de sistema. Documenta qué campos de Colección posee el equipo de marketing, qué valores provienen de HubSpot y qué hace el interruptor activo, siguiendo los pasos de la guía para entregar sitios de clientes.
Cuando estés listo para ampliar la integración en sí, la página de integración de HubSpot cubre los métodos de conexión basados en aplicaciones y las opciones de estilo de formularios a las que recurre primero el equipo interno de un cliente.
Preguntas frecuentes
¿Las presentaciones siguen apareciendo en los formularios de Webflow?
No. Al llamar a se detiene el gestor propio de Webflow, por lo que no se almacena nada en Webflow ni se envía ninguna notificación. Si necesitas una copia en Webflow, vuelve a escribirla mediante la API de datos o mantén un formulario independiente usando la aplicación nativa.preventDefault()
¿Puedo ejecutar esto sin una suscripción a Marketing Hub Professional?
Sí, para la ruta de envío. La API de formularios y las APIs de contactos y propiedades están disponibles para los alcances de aplicaciones privadas estándar. Solo la creación de campañas y la asociación de activos en el paso 7 requieren , así que, sin ella, mantienes la captura de leads pero pierdes los informes consolidados de campañas.marketing.campaigns.write
¿Qué ocurre si el visitante bloquea la cookie de HubSpot?
El envío sigue realizándose correctamente y el contacto se crea o se vincula mediante el correo electrónico. Pierdes el historial de visitas anteriores del usuario porque esa asociación depende del token . HubSpot no puede rellenar esa información a posteriori, así que considera la ausencia de la cookie como algo normal y no como un error.HubSpotUTK
¿Cuántos envíos por segundo puede gestionar la API de formularios?
Las cifras publicadas por HubSpot para el endpoint autenticado son de 100 solicitudes cada 10 segundos en los planes Free y Starter, y 150 en los planes Professional y Enterprise. Estos números provienen de un registro de cambios de 2021 y no aparecen en la página actual de límites, así que consulta las cabeceras de respuesta de límite de tasa para obtener los valores en tiempo real.
¿Puede un gestor de rutas servir campañas en diferentes portales de HubSpot?
Sí, y por eso el ID del portal proviene de la página y no del entorno. Sin embargo, un token de aplicación privada no puede abarcar varios portales, por lo que una configuración multiportal requiere un token por portal, resuelto en el servidor a partir del ID del portal entrante.
¿Funciona este enfoque en un sitio de Webflow sin Webflow Cloud?
No de forma segura. El token de la aplicación privada debe permanecer en el servidor y, sin un gestor de rutas, enviarías los datos desde el navegador con el token expuesto en el código fuente de la página. Si no tienes Webflow Cloud, utiliza la aplicación nativa de HubSpot o una plataforma de automatización.





