El pago y la entrega son dos eventos distintos en dos sistemas diferentes. Todo lo que sale mal en una tienda digital ocurre en el vacío que hay entre ambos.
La venta de un producto digital es el único tipo de comercio donde toda la transacción puede completarse en una pestaña del navegador. No se envía nada, no se recoge nada de un estante y el comprador espera recibir el archivo a los pocos segundos de pagar.
Esa velocidad es también lo que hace que sea fácil equivocarse. El pago ocurre en Stripe, la entrega ocurre de tu lado, y ninguno de los dos sistemas sabe si el otro ha terminado.
Esta guía construye ese puente correctamente: Webflow para la tienda, Stripe para el pago y un gestor de rutas (Route Handler) en Webflow Cloud que libera el producto solo después de que Stripe confirme que el dinero es real.
¿Qué necesitas para vender productos digitales con Stripe y Webflow?
Un sitio de Webflow para la tienda, una cuenta de Stripe con un producto y un precio, y un lugar en el lado del servidor para ejecutar dos gestores de rutas. El lado del servidor no es opcional, porque no se puede confiar en que un navegador confirme su propio pago.
Esto es lo que debes tener abierto antes de empezar:
- Un sitio de Webflow con las páginas que venderán el producto
- Una cuenta de Stripe en modo de prueba, con al menos un producto y un precio creados
- Tu clave secreta de Stripe y, más adelante, un secreto de firma de webhook que comience con
whsec_
- Un proyecto de Webflow Cloud que ejecute una aplicación Next.js, además de Node.js 22.13.0 o superior de forma local, el requisito mínimo establecido por Webflow CLI 2.x
Webflow Cloud está disponible desde el plan de sitio Starter gratuito en adelante, aunque montar la aplicación en un dominio personalizado requiere el plan Premium o superior. Sin embargo, antes de que nada de eso importe, debes decidir qué producto de Stripe vas a utilizar.
¿Necesita un mercado digital Stripe Connect?
Depende totalmente de a quién pertenezca el dinero. Si cada venta es tuya, Checkout estándar es la respuesta completa. En el momento en que a otra persona se le deba una parte de una venta, o que otro negocio esté cobrando pagos de sus propios clientes a través de tu producto, estás gestionando una plataforma, y Stripe tiene un producto específico para eso.
Stripe es muy claro sobre dónde se sitúa el límite. Connect existe para empresas que "gestionan pagos y mueven dinero entre múltiples partes", y su modelo de mercado se describe como el cobro de pagos a clientes y el pago automático de una parte a los vendedores. Nada de esto se puede hacer con Checkout estándar.
Busca la fila que describa tu situación:
La palabra "marketplace" se usa demasiado a la ligera en los briefs de proyectos, y elegir mal aquí sale caro porque Connect cambia la incorporación, el cumplimiento y la gestión de pagos, no solo una llamada a la API.
La estructura a continuación cubre la primera fila, que es el formato que adoptan la mayoría de las tiendas de productos digitales de un solo vendedor.
5 pasos para crear una tienda de productos digitales en Webflow Cloud
La estructura tiene dos manejadores de rutas y una regla: el navegador inicia un pago y solo Stripe puede confirmar que terminó. Todo lo que entrega un producto depende de ese segundo manejador.
Los pasos a continuación siguen un orden, ya que cada uno genera algo que el siguiente necesita:
1. Crea el producto y el precio en Stripe
Empieza en el Dashboard de Stripe en lugar de en el código, porque el objeto de precio que crees aquí es lo que tu tienda referenciará por ID. Crea el producto, añade un precio único y copia el ID de precio que empieza por price_.
Mantén el dinero en Stripe en lugar de en tu CMS de Webflow. Es tentador guardar el importe como un campo del CMS para que el equipo de diseño pueda editarlo, pero un precio que existe en dos lugares terminará por no coincidir, y lo que vean los compradores no será lo que se les cobre. Referencia el ID de precio de Stripe desde el CMS y deja que Stripe controle la cifra.
Termina este paso con un ID de precio que puedas pegar y un producto que aparezca en modo de prueba.
2. Crea el manejador de la sesión de Checkout
La tienda nunca habla directamente con Stripe. Envía un ID de precio a tu manejador de rutas, el cual crea una sesión de Checkout usando la clave secreta y devuelve una URL a la que redirigir.
La indirección existe debido a la clave secreta. Todo lo que se encuentra en un componente de cliente se envía al navegador, por lo que la clave debe permanecer en el lado del servidor de la solicitud, que es exactamente lo que te ofrece un Route Handler.
Crea el controlador enapp/api/checkout/route.ts
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'
export async function POST(request: NextRequest) {
const { priceId } = (await request.json()) as { priceId: string }
const secret = process.env.STRIPE_SECRET_KEY
const origin = request.headers.get('origin')
if (!secret || !priceId || !origin) {
return NextResponse.json({ error: 'Missing configuration' }, { status: 400 })
}
const stripe = new Stripe(secret, { maxNetworkRetries: 2 })
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: priceId, quantity: 1 }],
// No download link here. Nothing is delivered until payment settles.
success_url: `${origin}/thank-you?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${origin}/pricing`,
})
return NextResponse.json({ url: session.url })
}
Observa lo que no hay ahí: ni enlace de descarga, ni concesión de acceso, ni escritura en el CMS. La sesión es una solicitud de pago, no un pago en sí. Despliega esto y, al hacer clic en tu botón de compra, deberías llegar a una página de pago alojada en Stripe con el producto y el precio correctos.
3. Verifica el webhook de Stripe en el entorno de ejecución de Workers
Este paso separa una tienda que funciona de una que filtra productos. Stripe te notifica que un pago se realizó correctamente enviando una solicitud a tu endpoint, y cualquier otra persona en internet también puede hacerlo. La firma es lo que los distingue.
Webflow Cloud se ejecuta en Cloudflare Workers, y ese detalle del entorno de ejecución determina cómo realizas la verificación. En la versión de la biblioteca de Stripe para Workers, se selecciona un proveedor de Web Crypto de forma predeterminada en lugar del de Node, y ese proveedor solo realiza trabajos asíncronos. Por lo tanto, el método síncrono arroja un error en lugar de verificar nada. La propia plantilla de Cloudflare Worker de Stripe utiliza la forma asíncrona, y tú deberías hacer lo mismo.constructEventSubtleCryptoProvider cannot be used in a synchronous context
Añade el controlador enapp/api/stripe-webhook/route.ts
// app/api/stripe-webhook/route.ts
import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'
export async function POST(request: NextRequest) {
const signature = request.headers.get('stripe-signature')
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET as string
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
maxNetworkRetries: 2,
})
if (!signature) {
return NextResponse.json({ error: 'Unsigned request' }, { status: 400 })
}
// Read the body as text. Parsing it first changes the bytes Stripe signed.
const body = await request.text()
let event: Stripe.Event
try {
// constructEventAsync, never constructEvent: the Web Crypto provider
// that Workers uses cannot run in a synchronous context.
event = await stripe.webhooks.constructEventAsync(
body,
signature,
webhookSecret
)
} catch (err) {
const message = err instanceof Error ? err.message : 'Invalid signature'
return NextResponse.json({ error: message }, { status: 400 })
}
if (
event.type === 'checkout.session.completed' ||
event.type === 'checkout.session.async_payment_succeeded'
) {
const session = event.data.object as Stripe.Checkout.Session
// Re-read the session from the API. The copy embedded in the event
// can say payment_status: 'unpaid' for delayed methods like ACH.
const fresh = await stripe.checkout.sessions.retrieve(session.id, {
expand: ['line_items'],
})
if (fresh.payment_status !== 'unpaid') {
// Must tolerate being called twice with the same session ID.
await fulfillCheckout(fresh)
}
}
return NextResponse.json({ received: true })
}
Dos detalles son más importantes de lo que parecen. El cuerpo se lee con , porque Stripe firma los bytes exactos que envió y cualquier reanálisis de esa carga útil invalida la firma. Además, el controlador debe ser ligero, ya que Stripe reintenta cuando un 2xx es lento y Checkout espera hasta 10 segundos por tu respuesta antes de redirigir al comprador; por lo tanto, un controlador lento bloquea al cliente y corre el riesgo de una entrega duplicada. Una implementación correcta rechaza un POST falsificado con un 400 y acepta un evento de prueba de Stripe con un 200.request.text()
4. Entrega el producto solo después de que se active el webhook
El webhook debe gestionar la entrega, y la razón no es la que la gente supone. La guía de entrega de Stripe indica que no puedes depender de la página de destino porque no se garantiza que los clientes lleguen a ella: alguien puede pagar correctamente y perder la conexión antes de que la página se cargue.
Stripe también recomienda llamar a la misma función de entrega desde la página de destino, ya que los webhooks pueden retrasarse y un cliente presente debe ser atendido de inmediato. Trata esto como una segunda llamada a una función idempotente en lugar de una alternativa al webhook, razón por la cual la función debe tolerar ejecutarse dos veces para la misma sesión.
Dónde reside el archivo depende de su tamaño y sensibilidad. El almacenamiento de objetos de Webflow Cloud lo mantiene junto a la aplicación, y los buckets son privados, por lo que se accede al archivo a través de tu ruta en lugar de una URL pública. El patrón que funciona es que esa ruta verifique el derecho de acceso en cada solicitud y devuelva un enlace de corta duración, porque una URL estable está a un correo electrónico reenviado de convertirse en un producto gratuito.
Registra el derecho de acceso y luego genera el enlace bajo demanda. Cuando este paso es correcto, un pago de prueba completado produce una descarga que funciona para el comprador y una URL vacía que no hace nada para nadie más.
5. Registra la venta en el CMS de Webflow
Escribir la compra en una colección permite que el resto del sitio actúe como si supiera lo que sucedió: una página de cliente que enumera lo que alguien posee, un contador en el producto o una página privada abierta por la compra.
Usa el endpoint para crear elementos y recuerda que las claves son los slugs de campo de tu colección, no las etiquetas que se muestran en el Designer. Los elementos creados de esta manera están en borrador, por lo que existen en el CMS sin aparecer en el sitio activo hasta que se publiquen, que es lo que normalmente buscas para los registros de pedidos.fieldData
Gestiona un error 429 reintentando en lugar de descartar la escritura, ya que la API de datos impone un límite de solicitudes por minuto que varía según el plan del sitio. Una configuración funcional deja un elemento de CMS por cada compra de prueba completada, sin duplicados cuando Stripe reintenta el evento.
¿Qué causa que el pago de Stripe falle en Webflow Cloud? Consejos para solucionar problemas
Cuatro fallos explican la mayor parte de lo que sale mal aquí, y tres de ellos son silenciosos: la compilación que falla debido a una directiva de tiempo de ejecución, la verificación de firma que rechaza todos los eventos, la entrega duplicada por webhooks reintentados y un secreto que nunca llegó al entorno desplegado.
Resuélvelos en el orden siguiente, ya que cada uno oculta al siguiente:
La compilación falla tras añadir los Route Handlers
Causa: Una directiva en uno de los archivos de ruta. Webflow Cloud despliega Next.js a través del adaptador OpenNext Cloudflare, el cual no es compatible con el entorno de ejecución edge de Next.js, por lo que la directiva hace que la compilación falle en lugar de optimizar nada.export const runtime = 'edge'
Solución: Elimina la línea y vuelve a desplegar. Los Route Handlers ya se ejecutan en el entorno de Workers sin ella. Vale la pena comprobar esto primero siempre que una compilación de Webflow Cloud falle justo después de añadir nuevas rutas de API, ya que el error apunta al empaquetador (bundler) en lugar de a la directiva que lo causó, y la propia página de Webflow sobre cómo integrar tu propia aplicación sigue indicando a los usuarios de Next.js que la añadan.
Todos los webhooks fallan la verificación de firma
Causa: O bien el cuerpo se analizó antes de ser procesado con hash, o se utilizó la llamada de verificación síncrona. Ambos producen el mismo síntoma de un error 400 en cada evento, incluidos los envíos de prueba de Stripe.
Solución: Lee el payload con rawBody y pasa esa cadena directamente a la función de verificación. Si el error menciona un contexto síncrono, es que sigues llamando a una función síncrona en algún lugar. Si menciona una marca de tiempo (timestamp), revisa el reloj en lugar del código, ya que las librerías de Stripe rechazan por defecto los eventos fuera de una tolerancia de cinco minutos y un entorno con la hora mal ajustada hará que falle cada envío. Es recomendable probar esto con un evento de prueba real de Stripe en lugar de un POST manual, ya que una solicitud creada por ti fallará la verificación por la razón obvia de que no está firmada, lo cual no te dice nada sobre si el manejador es correcto.request.text()constructEventAsyncconstructEvent
Los compradores reciben el producto dos veces
Causa: Stripe reintenta un webhook si no recibe una respuesta de éxito inmediata, por lo que un proceso de entrega lento dentro del manejador provoca entregas repetidas y registros duplicados en el CMS para una misma compra.
Solución: Confirma la recepción del evento antes de realizar cualquier tarea lenta y haz que la entrega sea idempotente utilizando el ID de la sesión de Checkout como clave. Almacena ese ID con el registro del pedido y comprueba si ya existe antes de conceder el acceso; así, si el evento se reintenta, el sistema detectará que el trabajo ya está hecho y terminará. La idempotencia es la solución definitiva aquí; la velocidad solo reduce el margen de error, pero no lo elimina. Es fácil pasar esto por alto en las pruebas porque un manejador que funciona bien rara vez se reintenta, así que fuerza el caso: devuelve un 500 desde el manejador a propósito, deja que Stripe reintente y confirma que la segunda entrega detecta que el trabajo ya se realizó.
Funciona en local pero falla tras el despliegue
Causa: Las claves existen en tu archivo de entorno local pero no en el entorno desplegado, o se añadieron después del último despliegue y no se ha ejecutado ninguna compilación desde entonces.
Solución: Añade tanto la clave secreta como el secreto de firma del webhook en las variables de entorno de tu Webflow Cloud, márcalas como secretas y vuelve a desplegar. Estarán disponibles para la compilación y para la aplicación desplegada en tiempo de ejecución, y los valores secretos se ocultan en los registros de compilación. Asegúrate de no estar mezclando una clave de modo de prueba con un secreto de webhook de modo real, lo cual genera un error que parece un problema de código en lugar de uno de configuración.
Qué puedes construir a continuación con Stripe y Webflow
Una vez que el pago y la entrega son pasos separados que tú controlas, la tienda deja de ser una transacción única y se convierte en una cuenta: el historial de compras, las claves de licencia, los enlaces de descarga y las actualizaciones de suscripción dependen del mismo webhook.
Si quieres el proceso de pago sin la aplicación de Webflow Cloud, nuestra guía de Stripe Checkout cubre la configuración más sencilla. Para proteger las compras tras un inicio de sesión y que los compradores puedan volver a lo que han adquirido, la guía de autenticación y pagos añade la capa de cuenta en el mismo entorno de ejecución. Para las rutas de conexión que no requieren código, consulta la integración de Webflow y Stripe.
Para una personalización más profunda más allá de lo que cubren esas rutas, la documentación para desarrolladores de Webflow profundiza en los Route Handlers, el almacenamiento de objetos y el resto del entorno de ejecución de Webflow Cloud.
Preguntas frecuentes
¿Puedo vender productos digitales sin Webflow Cloud?
Sí, con Stripe Payment Links o un checkout alojado incrustado en una página de Webflow. Renuncias a la entrega desde el servidor, por lo que la entrega depende de la página de éxito en lugar de un evento de pago verificado. Eso funciona para archivos de bajo valor, pero es arriesgado para cualquier cosa que valga la pena piratear.
¿Por qué no entregar el archivo en la página de éxito?
Porque un comprador puede pagar y no llegar a cargarla nunca; la página no es una señal fiable. Stripe también recomienda llamar a tu función de entrega desde la página de aterrizaje para mayor rapidez, pero como una segunda llamada a la misma función idempotente, no como el único disparador.
¿Necesito Stripe Connect para un marketplace?
Solo si alguien más que tú debe recibir una parte de cada venta. Connect mueve dinero entre varias partes y paga a los vendedores automáticamente. Si cada venta pertenece a tu negocio, Checkout es la herramienta adecuada; Connect añade un proceso de registro que no necesitas.
¿Por qué falla la verificación de firma específicamente en Webflow Cloud?
Por el entorno de ejecución. Webflow Cloud funciona con Cloudflare Workers, donde el proveedor Web Crypto de Stripe es solo asíncrono. Llamar al método de verificación síncrono provoca un error de contexto, por lo que la variante asíncrona es obligatoria, no opcional.
¿Cómo evito que se comparta un enlace de descarga?
Emite enlaces de corta duración vinculados a una compra específica en lugar de exponer una ruta de archivo permanente. Registra el derecho de acceso al realizar la entrega y genera el enlace bajo demanda para controlar su caducidad y revocación.





