Cómo arreglar un formulario de Webflow que muestra JSON tras el envío

Cómo arreglar un webhook de formulario de Webflow que muestra JSON con una ruta de Webflow Cloud

Aprende a crear una ruta en Webflow Cloud que reenvíe los datos de un formulario a tu webhook y redirija a los navegadores.

Cómo arreglar un webhook de formulario de Webflow que muestra JSON con una ruta de Webflow Cloud

Ismail Ajagbe
Redactor técnico
Ver perfil del autor
Ismail Ajagbe
Redactor técnico
Ver perfil del autor
Índice de contenidos

Una acción de formulario personalizada hace que el navegador renderice el JSON de tu endpoint, pero una ruta de Webflow Cloud puede enviar a los visitantes a una página de agradecimiento en su lugar.

Imagina que configuraste la acción personalizada de un formulario de Webflow para que apunte a tu propio endpoint. Los datos llegaron, pero el visitante se encontró con una página en blanco que decía {"ok":true}. Una acción personalizada entrega el envío al navegador, que lo trata como una navegación y renderiza lo que sea que devuelva el endpoint.

Esto también evita el procesamiento de formularios de Webflow, por lo que el envío ya no se almacena en Webflow y no se envía ningún correo electrónico de notificación.

Si puedes mantener el procesamiento de Webflow, deja que Webflow se encargue del envío y añade un destino de Webhook en su configuración; así, Webflow almacenará el envío y mostrará el mensaje de éxito por sí mismo.

Cuando la confirmación depende de tu servidor, un manejador de rutas (Route Handler) en Webflow Cloud puede redirigir a los navegadores a una página de agradecimiento y responder al script de tu página con JSON.

¿Qué necesitas para arreglar un formulario de Webflow que muestra JSON en Webflow Cloud?

Webflow Cloud funciona con el plan gratuito Starter, pero montar la aplicación en un dominio personalizado requiere un plan Premium o superior.

Ten esto listo antes del primer despliegue:

  • Entorno de Webflow Cloud: Un sitio de Webflow con Webflow Cloud habilitado y un entorno donde desplegar; se requiere un plan Premium o superior si la aplicación se monta en un dominio personalizado.
  • Aplicación Next.js 15+: Una aplicación en Next.js 15 o superior, ya conectada al entorno y en proceso de despliegue; crearás app/api/submit/route.ts durante la compilación si aún no existe.
  • Solo npm: La documentación de bring-your-own-app de Webflow es clara: "Actualmente, Webflow Cloud solo admite el gestor de paquetes npm". Usa npm install; un paso de pnpm add o yarn add no funcionará.
  • URL del webhook de destino: La URL del receptor al que ya reenvías los datos, como un endpoint de CRM o un webhook entrante de Slack, además de cualquier token que requiera.
  • Página de agradecimiento publicada: Una página activa en el sitio de Webflow en una ruta que controles, como /thank-you, donde aterrizará la redirección tras un envío nativo.
  • Plan de pago para código de página: El script del paso 4 reside en el código personalizado de la página, lo cual requiere un plan de sitio de pago o un Workspace de pago.

Con la URL del receptor y la página de agradecimiento activas, la compilación depende de una única ruta capaz de distinguir entre un navegador y un script.

5 pasos para arreglar un webhook de formulario de Webflow que devuelve JSON en Webflow Cloud

La compilación consiste en un único manejador de rutas que reenvía los campos del formulario a tu webhook y luego responde a un envío nativo del navegador con una redirección 303 y a un fetch llamante con JSON, basándose en el encabezado Accept.

El paso 1 confirma cómo se envía el formulario actualmente, y los pasos 2 a 4 crean la ruta y conectan el formulario y el script de la página a ella. El paso 5 verifica ambas respuestas con curl.

1. Confirma que el formulario se envía de forma nativa a su endpoint actual

Envía el formulario una vez con las herramientas de desarrollador del navegador abiertas en la pestaña Red y observa qué sucede en la barra de direcciones. Si la URL cambia a la ruta del endpoint y el cuerpo de la página es JSON, significa que el navegador realizó una navegación completa y renderizó la respuesta como un documento.

En la pestaña Red, la solicitud de envío aparece como documento en lugar de fetch o XHR (XMLHttpRequest), y su tipo de contenido es . Busco esa combinación porque identifica un envío nativo.application/x-www-form-urlencoded

A continuación, selecciona el formulario en el Diseñador y busca en el panel de Configuración, bajo Enviar a, una acción personalizada con su URL y método. El método debería ser POST.

La causa es la acción personalizada. La documentación de formularios de Webflow indica que una acción personalizada omite por completo el manejo de formularios integrado de Webflow, por lo que los envíos no se envían ni se almacenan en Webflow, y no se pueden combinar con notificaciones de Webflow o por correo electrónico.

webflow.js solo toma el control del envío en formularios que se envían a Webflow, así que con una acción personalizada, el navegador realiza el POST de forma nativa y muestra lo que devuelva el endpoint. Ahora conoces dos hechos en los que se basan los pasos siguientes: el navegador está navegando y el endpoint responde con JSON sin importar quién realice el envío.

2. Bifurca la ruta según cómo se haya llamado

Crea (o reemplaza) el manejador de rutas para devolver una redirección 303 para un envío nativo del navegador y JSON para el script de la página, usando la cabecera Accept. Los navegadores suelen enviar una cabecera Accept que incluye en primer lugar en un envío nativo; el script de la página envía .text/htmlaccept: application/json

La ruta analiza cuerpos codificados como formulario o JSON, los verifica, reenvía los campos al webhook de destino y luego elige un tipo de respuesta.

Un 303 es el estado diseñado para el patrón post-redirect-get: el navegador lo sigue con un GET, por lo que recargar la página de agradecimiento nunca vuelve a enviar el formulario.

Mantengo la base de redirección en una variable de entorno en lugar de derivarla de la solicitud entrante, porque no quiero depender de que el origen que ve el Worker coincida con el dominio público; un explícito fija la redirección al dominio del visitante.SITE_URL

Esta ruta es un relé público, así que añade limitación de tasa antes de confiar en ella; el honeypot solo filtra a los bots perezosos.

Implementación del manejador de rutas

Crea o reemplaza con este manejador:app/api/submit/route.ts

import { NextResponse } from 'next/server';

type Fields = Record<string, string>;

async function readFields(request: Request): Promise<Fields> {
  const fields: Fields = {};
  try {
    const contentType = request.headers.get('content-type') ?? '';
    if (contentType.includes('application/json')) {
      const body: unknown = await request.json();
      if (typeof body !== 'object' || body === null || Array.isArray(body)) return fields;
      for (const [key, value] of Object.entries(body)) {
        if (typeof value === 'string') fields[key] = value;
      }
      return fields;
    }
    const form = await request.formData();
    for (const [key, value] of form.entries()) {
      if (typeof value === 'string') fields[key] = value;
    }
  } catch {
    // An unreadable body falls through as no fields, which fails the email check.
  }
  return fields;
}

export async function POST(request: Request) {
  const wantsJson = (request.headers.get('accept') ?? '').includes('application/json');
  const siteUrl = process.env.SITE_URL;
  const webhookUrl = process.env.DOWNSTREAM_WEBHOOK_URL;

  if (!siteUrl || !webhookUrl) {
    return NextResponse.json({ ok: false, error: 'Route is not configured' }, { status: 500 });
  }

  const fields = await readFields(request);

  // Honeypot: humans never see the "website" field, so a filled value means a bot.
  if (fields.website) {
    return wantsJson
      ? NextResponse.json({ ok: true })
      : NextResponse.redirect(new URL('/thank-you', siteUrl), 303);
  }

  if (!fields.email) {
    return wantsJson
      ? NextResponse.json({ ok: false, error: 'Email is required' }, { status: 400 })
      : NextResponse.redirect(new URL('/form-error', siteUrl), 303);
  }

  let forwarded = false;
  try {
    const forward = await fetch(webhookUrl, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(fields),
      signal: AbortSignal.timeout(10000),
    });
    forwarded = forward.ok;
  } catch {
    forwarded = false;
  }

  if (!forwarded) {
    return wantsJson
      ? NextResponse.json({ ok: false, error: 'Webhook rejected the submission' }, { status: 502 })
      : NextResponse.redirect(new URL('/form-error', siteUrl), 303);
  }

  return wantsJson
    ? NextResponse.json({ ok: true })
    : NextResponse.redirect(new URL('/thank-you', siteUrl), 303);
}

La ruta es un relé público hacia tu webhook, por lo que valida los campos que espera y descarta silenciosamente cualquier envío que rellene el campo oculto . Añade ese campo en el Diseñador como una entrada de texto llamada y ocúltalo con , y mantén las cargas útiles pequeñas enviando solo los campos que el receptor necesita.websitewebsitedisplay: none

La ruta lee las claves y , así que comprueba que la entrada de correo electrónico se envíe bajo la clave ; un campo renombrado como envía cada envío a .emailwebsiteemailEmail-2/form-error

El archivo no tiene ninguna línea . Webflow Cloud despliega Next.js a través del adaptador OpenNext Cloudflare, y ese adaptador no admite el objetivo de tiempo de ejecución edge de Next.js, por lo que la directiva genera una compilación defectuosa.export const runtime = 'edge'

La ruta ahora devuelve una redirección para un envío nativo y JSON para un script, y la misma lógica de reenvío sirve para ambos.

3. Configura las variables de entorno y el destino de redirección

Añade y en la configuración del entorno de Webflow Cloud donde se despliega la aplicación. es el origen publicado del sitio, como , sin ruta final. es el receptor al que reenvías, y si esa URL contiene un token, guárdalo como un secreto.SITE_URLDOWNSTREAM_WEBHOOK_URLSITE_URLhttps://www.example.comDOWNSTREAM_WEBHOOK_URL

La documentación sobre 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 su aplicación y para la aplicación desplegada en tiempo de ejecución", y los secretos se ocultan en los registros de compilación.

Para las ejecuciones locales, recomiendo configurar las mismas dos claves en un .env.local archivo en la raíz del proyecto. Luego, publique las páginas a las que redirige la ruta: una página de agradecimiento en /thank-you y una página de error en /form-error, ambas páginas sencillas de Webflow creadas en el Diseñador. Las rutas en el enrutador y los slugs de las páginas publicadas deben coincidir exactamente, incluyendo las mayúsculas y minúsculas.

Ambas claves llegan ahora a la compilación y a la ruta en ejecución, por lo que el protector en la parte superior del controlador deja de devolver su error de configuración una vez que la aplicación se vuelve a desplegar.

4. Intercepte el envío en el código personalizado de la página

Añada un script a la página del formulario para que el envío se realice a través de fetch y el visitante permanezca en la página. Recomiendo este camino cuando desee un mensaje de éxito en el mismo lugar. Asigne al formulario el ID HTML contact-form.

El script reutiliza los elementos de mensaje de éxito y error que se encuentran dentro del bloque de formulario, por lo que puede editar ese texto en el Diseñador en lugar de crear un elemento separado. El script llama a la ruta con accept: application/json, lo que hace que la ruta devuelva JSON en lugar de una redirección, luego oculta el formulario y muestra el estado de éxito.

La ruta de montaje se escribe manualmente en la URL porque este código reside en una página de Webflow, fuera de la aplicación, donde nada lo inyecta automáticamente por usted.

Script de código personalizado de la página

Añada el script a la página del formulario antes de la etiqueta de cierre del cuerpo, cambiando /app por la ruta de montaje de su aplicación:

<script>
  (function () {
    var form = document.getElementById('contact-form');
    if (!form) return;
    var wrapper = form.closest('.w-form');
    var done = wrapper && wrapper.querySelector('.w-form-done');
    var fail = wrapper && wrapper.querySelector('.w-form-fail');

    form.addEventListener('submit', async function (event) {
      event.preventDefault();
      event.stopImmediatePropagation();
      var body = Object.fromEntries(new FormData(form).entries());
      var button = form.querySelector('[type="submit"]');
      if (button) button.disabled = true;
      if (fail) fail.style.display = 'none';

      try {
        var response = await fetch('/app/api/submit', {
          method: 'POST',
          headers: { 'content-type': 'application/json', accept: 'application/json' },
          body: JSON.stringify(body)
        });
        if (!response.ok) throw new Error('Submit failed');
        form.style.display = 'none';
        if (done) {
          done.style.display = 'block';
          done.setAttribute('tabindex', '-1');
          done.focus();
        }
      } catch (err) {
        if (button) button.disabled = false;
        if (fail) {
          fail.style.display = 'block';
          fail.setAttribute('tabindex', '-1');
          fail.focus();
        }
      }
    }, true);
  })();
</script>

El oyente se ejecuta en la fase de captura y llama a stopImmediatePropagation(), por lo que no se ejecuta ningún oyente de envío posterior, incluido el oyente delegado que webflow.js mantiene en la página. Con una acción personalizada configurada, webflow.js deja el envío al navegador de todos modos, por lo que esto solo importa si el formulario aún se envía a Webflow.

Mover el foco al mensaje permite que los lectores de pantalla anuncien el resultado, tal como lo hace el propio controlador de Webflow. El return inicial protege las páginas que reutilizan el mismo código personalizado sin el formulario: allí, el script no hace nada en lugar de generar un error.

El script espera .w-form, .w-form-done y .w-form-fail para identificar el contenedor del bloque de formulario y sus mensajes de éxito y error, así que verifíquelos con su marcado publicado y vuelva a comprobarlos si este cambia.

Luego, apunte el formulario en sí a la ruta: en el panel de configuración del formulario, en "Enviar a", reemplace los destinos de Webflow y las notificaciones por correo electrónico con una acción personalizada configurada en /app/api/submit (su ruta de montaje seguida de /api/submit) y el método POST.

Los visitantes sin scripts ahora envían los datos de forma nativa a la ruta y obtienen la redirección 303, mientras que los visitantes con scripts permanecen en la página. Después de que la aplicación se despliega, enviar el formulario lo oculta y muestra el mensaje de éxito del bloque de formulario en el mismo lugar, sin cambiar la barra de direcciones.

5. Despliegue y pruebe ambos caminos de respuesta

Despliegue la aplicación en Webflow Cloud, luego pruebe la ruta desde la línea de comandos para verificar el comportamiento del servidor en lugar de la interpretación del navegador. Estas dos solicitudes solo difieren en la codificación del cuerpo y el encabezado Accept, que es exactamente la diferencia sobre la que se ramifica la ruta.

Ejecute primero la forma de envío nativo y lea la línea de estado y el encabezado Location:

curl -i -X POST https://www.example.com/app/api/submit \
  -d "name=Test" -d "email=test@example.com"

Debería ver un estado 303 y un encabezado Location que apunta a https://www.example.com/thank-you.

Ahora envíe la forma que envía el script de la página:

curl -i -X POST https://www.example.com/app/api/submit \
  -H "content-type: application/json" \
  -H "accept: application/json" \
  -d '{"name":"Test","email":"test@example.com"}'

Esta debería devolver un 200 con el cuerpo {"ok":true}. Si alguna de las solicitudes produce la respuesta de la otra, primero inspeccionaría la verificación de Accept en la ruta.

Por último, confirma que el receptor de destino registró dos envíos, uno por cada solicitud. Con ambas comprobaciones superadas, un envío correcto en el sitio activo mostrará una página de agradecimiento o un mensaje de éxito en la misma página, en lugar de una página con código JSON.

¿Por qué un webhook de formulario de Webflow muestra JSON en lugar de un mensaje de éxito?

La mayoría de los fallos se deben a que el navegador recibe una respuesta automática o a que la solicitud fetch no incluye la ruta de montaje de la aplicación. El resto se debe a problemas de compilación o a una configuración incompleta en el entorno de despliegue.

La barra de direcciones cambia a la ruta de acceso y la página muestra {"ok":true}

Causa: El formulario se envió de forma nativa y la ruta devolvió JSON al navegador. Un navegador trata un POST de formulario como una navegación y renderiza lo que recibe como el nuevo documento; como el cuerpo JSON no tiene marcado, se muestra como texto plano.

Este es el resultado predeterminado cuando la acción de un formulario apunta a un endpoint diseñado para un consumidor de webhooks, y no significa que el envío haya fallado: para cuando ves el JSON, el webhook de destino ya ha recibido los campos.

Solución: Recomiendo que la ruta devuelva una redirección 303 para los envíos desde el navegador, utilizando la rama del encabezado Accept en la ruta. Luego, verifica que el script de la página se esté ejecutando: si el ID del formulario en el Diseñador no coincide , el script se detiene y el formulario recurre a la navegación nativa.contact-form

Abre la pestaña Red y confirma que la solicitud de envío aparece como fetch y no como document. Si sigue apareciendo como document, casi siempre se debe a una discrepancia en el ID.

La llamada fetch devuelve un error 404 en el sitio activo, pero la ruta funciona correctamente en next dev

Causa: A la URL de la solicitud le falta la ruta de montaje de la aplicación. En Webflow Cloud, la aplicación se monta bajo una ruta base en el sitio, y esa ruta se inyecta durante la compilación en lugar de leerse desde .next.config

Las llamadas fetch del lado del cliente no se reescriben: la documentación de Webflow sobre aplicaciones personalizadas indica que "las llamadas fetch del lado del cliente deben incluir manualmente la ruta base".

Localmente, al no haber ruta de montaje, se resuelve correctamente, por lo que el error solo aparece tras el despliegue./api/submit

Solución: Para el script en la página de Webflow, escribe la ruta montada completa manualmente, tal como lo hace el script de la página con . Para cualquier formulario renderizado dentro de la propia aplicación Next.js, añade una variable configurada con tu ruta de montaje, ya que Webflow Cloud no la crea automáticamente, y anteponla a la URL del fetch para que el mismo componente funcione tanto localmente como en producción./app/api/submitNEXT_PUBLIC_BASE_PATH

Confirma la solución solicitando la ruta montada directamente con curl; un error 404 significa que la ruta es incorrecta, mientras que un 405 suele indicar que la ruta se resuelve pero el método de solicitud no es POST.

La compilación en Webflow Cloud falla en un archivo de ruta que funciona correctamente en next dev

Causa: El detonante habitual es una línea en un archivo de ruta. La palabra "edge" tiene dos significados distintos y la documentación de Webflow utiliza ambos.export const runtime = 'edge'

Webflow Cloud ejecuta aplicaciones en Cloudflare Workers, una plataforma edge, y la página de aplicaciones personalizadas lo describe así: "Webflow Cloud despliega tu aplicación usando el runtime Edge, lo que permite un alojamiento rápido y distribuido globalmente". Esa misma página te indica que añadas la directiva del runtime edge de Next.js a las rutas de la API.

La ruta puede ejecutarse en la plataforma Workers sin esa directiva. La directiva selecciona el objetivo del runtime edge de Next.js por separado, el cual no es compatible con el adaptador de OpenNext para Cloudflare que utiliza Webflow Cloud para desplegar Next.js. Por lo tanto, añadir esa línea rompe la compilación, incluso cuando el desarrollador sigue la documentación al pie de la letra.

Solución: Elimina la directiva de todos los archivos de ruta y vuelve a desplegar. La ruta ya se ejecuta en la plataforma Workers sin ella. Si la aplicación utiliza middleware, mantenlo en lugar de un Next 16 , porque la documentación de personalización del framework de Webflow indica que "el middleware de runtime de Node.js no es compatible. Solo el middleware de runtime Edge funciona en el runtime de Workers".middleware.tsproxy.ts

La ruta devuelve un error 500 en Webflow Cloud, pero el mismo envío funciona correctamente de forma local

Causa: SITE_URL o DOWNSTREAM_WEBHOOK_URL está definido en tu entorno local .env.local y no en el entorno de Webflow Cloud, por lo que el protector al inicio de la ruta devuelve el error de configuración.

Ambos conjuntos de variables son independientes: los valores de tu máquina nunca llegan al Worker desplegado, así que debes configurarlos en ambos sitios. Es lo primero que reviso cuando una ruta funciona en local pero falla al desplegarse.

Solución: Añade ambas variables al entorno donde se despliega la aplicación y vuelve a desplegar para que la compilación las detecte. Luego, lanza una petición curl con formato JSON a la ruta para leer el mensaje de error en el cuerpo; el protector de configuración devuelve JSON sin importar la cabecera Accept.

Si el cuerpo indica que el webhook rechazó el envío en lugar de decir que la ruta no está configurada, significa que las variables están presentes y el problema se ha trasladado al receptor intermedio, que es donde debes mirar ahora.

Qué puedes construir a continuación con Webflow Cloud y los formularios de Webflow

El mismo manejador puede enviar cada formulario a un segundo receptor sin tener que tocar el formulario en el Designer de nuevo, o validar un campo con un servicio externo antes de reenviarlo, tal como hace una ruta de validación de teléfono al comprobar números. Una vez que todos los formularios del sitio envían datos a una ruta que tú controlas, ya no solo decides la respuesta.

Para una personalización más profunda más allá de una ruta de formulario única, la documentación para desarrolladores de Webflow cubre los entornos y límites de Webflow Cloud.

Preguntas frecuentes

¿La redirección tiene que ser un 303 o sirve un 302?

Sí, un 302 también funciona en los navegadores porque el estándar Fetch convierte el POST en un GET, aunque se prefiere el 303. Al fin y al cabo, expresa explícitamente esa intención para cualquier cliente.

¿Esto funcionará con una aplicación Astro en Webflow Cloud?

Sí. La lógica es la misma en un endpoint de servidor de Astro: reenvía el cuerpo analizado y luego comprueba la cabecera Accept para elegir entre una redirección o JSON. Solo cambia el asistente de respuesta; el endpoint de Astro devuelve un Response estándar con un estado 303 y una cabecera Location .

¿Qué pasa si los bots ignoran el honeypot y siguen enviando datos a la ruta?

Comprueba la cabecera Origin en la ruta. Los navegadores que envían datos desde tu sitio incluyen el origen de tu sitio, por lo que la ruta puede descartar cualquier POST cuyo Origin no coincida con SITE_URL. Algunos bots programados omiten la cabecera, aunque uno decidido puede falsificarla. Al igual que con el honeypot, devuelve la respuesta de éxito normal para que el bot no aprenda nada.

¿Por qué falla el script de la página en el dominio de pruebas webflow.io?

El script envía datos a una ruta relativa en el dominio que sirvió la página. Si la aplicación solo está montada en tu dominio personalizado, es probable que la misma ruta relativa en webflow.io no tenga una aplicación detrás, por lo que la petición falla. Prueba dónde está montada la aplicación; cambiar a una URL absoluta hace que la petición sea de origen cruzado, lo que requiere cabeceras CORS.


Última actualización
October 19, 2026
Categoría

Artículos relacionados


verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo
verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo

Empieza gratis

Prueba Webflow todo el tiempo que quieras con nuestro plan Starter gratuito. Compra un plan de sitio de pago para publicar, alojar y desbloquear funciones adicionales.

Empieza, es gratis
Ver demostración

Prueba Webflow todo el tiempo que quieras con nuestro plan Starter gratuito. Compra un plan de sitio de pago para publicar, alojar y desbloquear funciones adicionales.