La mayoría de los errores de la API de inventario se deben a que se envía un ID de producto donde debería ir un ID de SKU; una ruta protegida en Webflow Cloud mantiene el token en el servidor mientras soluciona este problema.
El endpoint de inventario de Webflow lee y actualiza el stock de un SKU a la vez, y falla de formas que el cuerpo del error rara vez explica. Un 404 suele significar que se usó un ID de producto en lugar de un ID de SKU. Un 409 indica que el sitio no tiene activado el comercio electrónico, y llamar al endpoint desde el navegador expondría tu token de sitio de todos modos.
En este artículo, configuraremos una ruta proxy segura para la API de inventario en Webflow Cloud.
¿Qué necesitas para usar un proxy con la API de inventario en Webflow?
Necesitas cinco cosas:
- Sitio de Webflow con comercio electrónico: El endpoint de inventario devuelve un 409 cuando el comercio electrónico no está habilitado en el sitio, así que verifica eso primero.
- Token del sitio de Webflow: Créalo en la configuración del sitio, en Aplicaciones e integraciones con
ecommerce:read,ecommerce:writeycms:read, y trátalo como un secreto desde el principio. - Node.js 22.13.0 o posterior: Ese es el requisito mínimo del motor de Webflow CLI 2.x, que es más estricto que el requisito general de Node.js 22 para Webflow Cloud.
- npm: Webflow Cloud solo admite npm, así que instala y bloquea las dependencias con npm.
- IDs de SKU: La ruta del endpoint es
/v2/collections/{sku_collection_id}/items/{sku_id}/inventory, por lo que necesitas el ID de la colección de SKU y el ID de cada SKU que quieras leer o actualizar.
Los IDs de SKU son donde la mayoría de los proyectos fallan, así que el paso 1 es encontrarlos antes de escribir cualquier código.
6 pasos para usar un proxy con la API de inventario en Webflow Cloud
La estructura consiste en un Route Handler dinámico que lee y actualiza el stock de un SKU, una middleware.ts puerta de enlace que mantiene las escrituras protegidas mediante un secreto compartido y un componente de cliente que llama a la ruta a través de la ruta de montaje.
1. Encuentra el ID de la colección de SKU y tus IDs de SKU
Cada producto en Webflow Ecommerce tiene al menos un SKU, y el inventario pertenece al SKU, no al producto. Un producto con tallas o colores tiene un SKU por variante, cada uno con su propio recuento de stock.
El endpoint de inventario utiliza el ID de elemento del SKU y el ID de la colección de SKUs, por lo que un ID de producto copiado desde el Diseñador o la lista de productos devolverá un 404 incluso si el token y la ruta son correctos.
El endpoint de productos devuelve cada producto con sus SKUs anidados, y la lista de colecciones te proporciona el ID de la colección de SKU. Exporta el ID de tu sitio y tu token en una terminal, luego ejecuta ambas solicitudes:
curl -s "https://api.webflow.com/v2/sites/$SITE_ID/products?limit=100" \
-H "Authorization: Bearer $WEBFLOW_SITE_TOKEN" \
| jq '.items[] | {product: .product.fieldData.name, skus: [.skus[] | {id, name: .fieldData.name}]}'
curl -s "https://api.webflow.com/v2/sites/$SITE_ID/collections" \
-H "Authorization: Bearer $WEBFLOW_SITE_TOKEN" \
| jq '.collections[] | {id, displayName, slug}'
El primer comando enumera todos los productos con sus IDs de SKU. El segundo comando muestra la colección llamada SKUs, que contiene el ID que necesitas. Si alguna de las solicitudes devuelve un 409, activa el comercio electrónico para el sitio antes de continuar. Ahora tienes un ID de colección y un ID de SKU para cada variante que planeas rastrear.
2. Prepara la aplicación Next.js con npm y la CLI de Webflow
Install the Webflow CLI and create the app with npm, since the platform fixes both the package manager and the Node version. The CLI package is @webflow/webflow-cli, and its 2.x releases require Node.js 22.13.0 or later, which is why the version check comes first.
Instala la CLI de Webflow y crea la aplicación con npm, ya que la plataforma fija tanto el gestor de paquetes como la versión de Node. El paquete de la CLI es , y sus versiones 2.x requieren Node.js 22.13.0 o superior, por lo que la comprobación de la versión es lo primero.
node --version
npm install -g @webflow/webflow-cli
npx create-next-app@15 inventory-app
cd inventory-app
Comprueba tu versión de Node, luego instala la CLI y crea la estructura de la aplicación:create-next-app@15middleware.tscreate-next-appapp/src/
Fijar next.config mantiene la estructura en la versión donde es el nombre de archivo estándar, lo cual es importante para el control de escritura en el paso 5. Elige el App Router cuando lo solicite, ya que los Route Handlers residen en package-lock.json, y rechaza la opción de directorio para que cada ruta en esta compilación permanezca donde se muestra.
Deja en paz: la documentación de configuración de Webflow dice que no necesitas añadir un adaptador, una ruta base ni un modo de salida. Al final de este paso, tendrás un proyecto Next.js 15 con un , la CLI de Webflow en tu ruta y una versión de Node que la CLI acepta.
3. Almacena el token y el ID de la colección como variables de entorno
Coloca el token y el ID de la colección de SKU en el entorno de Webflow Cloud que contiene tu ruta de montaje, no en el repositorio.
La página 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 y para la aplicación desplegada en tiempo de ejecución", y los secretos se ocultan en los registros de compilación.
- Cuatro variables cubren esta compilación:
WEBFLOW_API_TOKENwebflow auth login.env - WEBFLOW_SITE_TOKEN: Márcala como secreta. El Route Handler la envía como un token de portador (bearer token) en cada llamada de inventario. Evito el nombre aquí porque escribe su propio token bajo ese nombre en , y mezclar ambos es una forma fácil de terminar llamando a la API con los permisos incorrectos.
- WEBFLOW_SKU_COLLECTION_ID: El ID de la colección de SKU del paso 1.
- INVENTORY_ROUTE_SECRET: Márcala como secreta. El middleware la compara con una cabecera de solicitud antes de que cualquier escritura llegue al manejador, para que los trabajos de sincronización puedan escribir y los navegadores no.
/inventory
NEXT_PUBLIC_BASE_PATH:next dev La ruta de montaje del entorno, por ejemplo .env.local. Webflow Cloud no crea esta variable, así que añádela tú mismo.NEXT_PUBLIC_BASE_PATHnext dev/inventory
Para , coloca las tres primeras en , idealmente con un token de desarrollo independiente que tenga los mismos permisos. Deja sin configurar localmente, porque sirve la aplicación en la raíz y un prefijo haría que la petición local devolviera un error 404.
4. Escribe el Route Handler de inventarioinventoryTypequantityupdateQuantity
Crea :app/api/inventory/[skuId]/route.ts
// app/api/inventory/[skuId]/route.ts
const API = 'https://api.webflow.com/v2/collections';
const ID_PATTERN = /^[a-f0-9]{24}$/;
type Inventory = { id: string; quantity: number; inventoryType: 'finite' | 'infinite' };
function config() {
const token = process.env.WEBFLOW_SITE_TOKEN;
const collectionId = process.env.WEBFLOW_SKU_COLLECTION_ID;
return token && collectionId ? { token, collectionId } : null;
}
async function callWebflow(skuId: string, init: RequestInit = {}) {
const cfg = config();
if (!cfg) {
return Response.json({ error: 'Inventory route is not configured' }, { status: 500 });
}
const upstream = await fetch(`${API}/${cfg.collectionId}/items/${skuId}/inventory`, {
...init,
headers: {
Authorization: `Bearer ${cfg.token}`,
'Content-Type': 'application/json',
accept: 'application/json',
},
});
const data = await upstream.json().catch(() => null);
if (!upstream.ok) {
return Response.json(
{ error: 'Webflow rejected the request', status: upstream.status, detail: data },
{ status: upstream.status, headers: retryHeader(upstream) },
);
}
const { id, quantity, inventoryType } = data as Inventory;
return Response.json({ id, quantity, inventoryType });
}
function retryHeader(res: Response): Record<string, string> {
const retryAfter = res.headers.get('retry-after');
return retryAfter ? { 'Retry-After': retryAfter } : {};
}
type Params = { params: Promise<{ skuId: string }> };
export async function GET(_request: Request, { params }: Params) {
const { skuId } = await params;
if (!ID_PATTERN.test(skuId)) {
return Response.json({ error: 'Invalid SKU ID' }, { status: 400 });
}
return callWebflow(skuId);
}
export async function PATCH(request: Request, { params }: Params) {
const { skuId } = await params;
if (!ID_PATTERN.test(skuId)) {
return Response.json({ error: 'Invalid SKU ID' }, { status: 400 });
}
const body = await request.json().catch(() => null);
const inventoryType = body?.inventoryType;
const hasQuantity = Number.isInteger(body?.quantity);
const hasUpdate = Number.isInteger(body?.updateQuantity);
if (inventoryType !== 'finite' && inventoryType !== 'infinite') {
return Response.json({ error: 'inventoryType must be finite or infinite' }, { status: 400 });
}
if (inventoryType === 'finite' && hasQuantity === hasUpdate) {
return Response.json(
{ error: 'Send exactly one of quantity or updateQuantity' },
{ status: 400 },
);
}
const update =
inventoryType === 'infinite'
? { inventoryType }
: hasQuantity
? { inventoryType, quantity: body.quantity }
: { inventoryType, updateQuantity: body.updateQuantity };
return callWebflow(skuId, { method: 'PATCH', body: JSON.stringify(update) });
}
El manejador valida el ID de SKU de la URL y llama a Webflow con el token del entorno. GET devuelve solo los campos de stock, y PATCH acepta un cuerpo que coincide con lo que permite el quantitypunto de conexión de actualizaciónupdateQuantity: es obligatorio, establece un recuento absoluto y suma o resta al actual.
El manejador requiere exactamente uno de Retry-After o para stock finito, de modo que un trabajo de sincronización no pueda enviar una actualización ambigua, y descarta ambos campos cuando el SKU se establece como infinito.
Validar el ID de SKU como una cadena hexadecimal de 24 caracteres significa que nada de lo que envíe un llamador puede cambiar la ruta ascendente. Los códigos de estado de Webflow pasan sin cambios, junto con cualquier cabecera export const runtime = 'edge' en un error 429, para que un trabajo de sincronización pueda esperar el tiempo que Webflow indique.
Deja npm run dev fuera de este archivo. La página de "bring-your-own-app" todavía lo sugiere para rutas de API, pero apunta al tiempo de ejecución edge de Next.js, que el adaptador OpenNext Cloudflare detrás de Webflow Cloud no admite, y la compilación fallará. La ruta ya se ejecuta en Cloudflare Workers sin él./api/inventory/<sku-id>quantityinventoryType
Ejecuta y solicita con uno de tus IDs de SKU. Un cuerpo JSON con y significa que el token y ambos IDs coinciden.
Añade un middleware.ts archivo en la raíz del proyecto que lea y exija un secreto compartido en todos los demás métodos. El nombre del archivo es importante en esta plataforma.
La documentación de personalización del framework de Webflow Cloud indica: "El middleware del entorno de ejecución de Node.js no es compatible. Solo funciona el middleware del entorno de ejecución Edge en los Workers". Next.js 16 cambia el nombre de middleware a proxy, pero proxy se ejecuta en el entorno de Node y no puede optar por Edge, por lo que un archivo renombrado nunca se ejecutará aquí.
La puerta de enlace compara los resúmenes SHA-256 de ambos valores, ocultando la longitud del secreto y funcionando igual en next dev y en los Workers. Eso evita crypto.subtle.timingSafeEqual, que existe en el entorno de ejecución de Workers desplegado pero no bajo next dev local.
Crea middleware.ts junto a package.json:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
async function sha256Hex(value: string): Promise<string> {
const data = new TextEncoder().encode(value);
const digest = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(digest))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
export async function middleware(request: NextRequest) {
if (request.method === 'GET') {
return NextResponse.next();
}
const expected = process.env.INVENTORY_ROUTE_SECRET;
const provided = request.headers.get('x-inventory-secret') ?? '';
if (!expected) {
return NextResponse.json({ error: 'Route secret is not set' }, { status: 500 });
}
const [a, b] = await Promise.all([sha256Hex(expected), sha256Hex(provided)]);
if (a !== b) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.next();
}
export const config = {
matcher: '/api/inventory/:path*',
};
Un trabajo de sincronización que envía x-inventory-secret ahora puede aplicar PATCH al inventario, mientras que un navegador solo puede leer. El GET permanece abierto porque una tienda necesita mostrar el inventario, y cada lectura cuesta una llamada contra el límite de tasa del token de 120 solicitudes por minuto en sitios de Ecommerce.
Si una página de producto muy visitada supera ese límite, almacena las lecturas en caché durante unos segundos en el almacenamiento de clave-valor de Webflow Cloud en lugar de usar Cache-Control, que Webflow Cloud reemplaza en cada respuesta.
Un PATCH sin el encabezado ahora devuelve un 401 tanto localmente como después del despliegue, y un GET devuelve el inventario como antes.
6. Llama a la ruta desde el cliente con la ruta base
Prefija cada fetch del lado del navegador con la ruta de montaje, porque Webflow Cloud no reescribe las solicitudes del cliente por ti. La página de "bring-your-own-app" lo explica claramente: "Las llamadas fetch del lado del cliente deben incluir manualmente la ruta base para llegar correctamente a tus endpoints". El código del servidor nunca ve este problema, por lo que el error solo aparece una vez que un componente se ejecuta en el navegador.
Una insignia de inventario mínima lee la ruta desde el navegador:
// app/components/StockBadge.tsx
'use client';
import { useEffect, useState } from 'react';
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? '';
export function StockBadge({ skuId }: { skuId: string }) {
const [state, setState] = useState<'loading' | 'ok' | 'error'>('loading');
const [stock, setStock] = useState<{ quantity: number; inventoryType: string } | null>(null);
useEffect(() => {
fetch(`${basePath}/api/inventory/${skuId}`)
.then(async (res) => {
if (!res.ok) throw new Error(String(res.status));
setStock(await res.json());
setState('ok');
})
.catch(() => setState('error'));
}, [skuId]);
if (state === 'loading') return <span>Checking stock</span>;
if (state === 'error') return <span>Stock unavailable</span>;
if (stock?.inventoryType === 'infinite') return <span>In stock</span>;
return <span>{stock && stock.quantity > 0 ? `${stock.quantity} in stock` : 'Sold out'}</span>;
}
Renderiza <StockBadge skuId="..." /> desde app/page.tsx con uno de tus ID de SKU, luego despliega con la CLI de Webflow al entorno que contiene la ruta de montaje; el comando de despliegue y las instrucciones para vincular el proyecto están en la página de bring-your-own-app.
Una vez que finalice el despliegue, abre la ruta de montaje en el sitio y la insignia cambiará de "Comprobando inventario" al recuento en tiempo real.
¿Qué causa que las llamadas a la API de inventario de Webflow fallen?
La mayoría de los fallos de inventario provienen de los ID y alcances que espera el endpoint; el resto proviene de las reglas de compilación y enrutamiento de Webflow Cloud. Busca el síntoma y su causa a continuación.
La ruta de inventario devuelve 404 o 409 desde Webflow
Causa: Un 404 suele significar que se utilizó un ID de producto donde el endpoint necesitaba un ID de SKU, o que el ID de la colección pertenece a la colección de Productos en lugar de a la de SKUs. Un 409 significa que el sitio no tiene habilitado el Ecommerce, por lo que el endpoint rechaza todas las llamadas.
Solución: Vuelve a ejecutar las dos solicitudes del paso 1 y copia el ID de SKU del skus array, no el ID del producto, y el ID de la colección llamada SKUs. Actualiza WEBFLOW_SKU_COLLECTION_ID si era incorrecto y vuelve a desplegar. Para un 409, habilita el Ecommerce en el sitio; ningún cambio de código lo solucionará.
La ruta de inventario devuelve 401, 403 o 429 desde Webflow
Causa: Un 401 o 403 significa que al token le falta un alcance. Las lecturas necesitan ecommerce:read y las escrituras necesitan ecommerce:write, por lo que un token que lee correctamente puede fallar en un PATCH. Un 401 tras el despliegue también puede significar que se pegó un token de desarrollo, o el propio WEBFLOW_API_TOKEN de la CLI, en el secreto de producción.
Un 429 significa que el token agotó su asignación por minuto, lo cual es fácil de alcanzar cuando cada vista de página lee el inventario.
Solución: Regenera el token del sitio con ambos alcances de Ecommerce, actualiza WEBFLOW_SITE_TOKEN y vuelve a desplegar. Ante un error 429, haz que los trabajos de sincronización esperen el Retry-After valor que la ruta transmite antes de reintentar, y almacena en caché las lecturas de la tienda en el almacenamiento de clave-valor.
Si la ruta devuelve su propio error 500 con el mensaje "Inventory route is not configured", falta una variable en el entorno de Webflow Cloud.
La compilación de Webflow Cloud falla en la ruta de inventario
Causa: La ruta declara export const runtime = 'edge', algo que el adaptador de OpenNext para Cloudflare no admite. La página de "bring-your-own-app" describe que Webflow Cloud se despliega "usando el tiempo de ejecución Edge", es decir, la plataforma Cloudflare Workers, y luego indica añadir la directiva a las rutas de la API, por lo que un desarrollador que lea la primera frase concluye erróneamente que la segunda es obligatoria.
La otra causa común es una node:fs importación, que suele añadirse para almacenar en caché la última carga útil; Workers solo la proporciona a partir de la fecha de compatibilidad 2025-09-01, que es posterior a la fecha de la configuración de ejemplo de Webflow Cloud.
Solución: Elimina la runtime exportación de todas las rutas y vuelve a desplegar; no hace falta reemplazarla, ya que la ruta ya se ejecuta en Workers. Para un fallo de node:fs , elimina la importación y mantén el controlador sin estado, usando el almacenamiento de clave-valor para cualquier dato que deba persistir entre solicitudes.
El distintivo de stock da un error 404 en el navegador pero funciona desde el servidor
Causa: La solicitud fetch del lado del cliente buscó /api/inventory/... en la raíz del sitio, mientras que la aplicación está montada en una ruta como /inventory, por lo que la solicitud no encuentra la aplicación y devuelve la página 404 del sitio de Webflow. Un error de análisis JSON en un cuerpo HTML suele ser la primera pista.
Solución: Confirma que NEXT_PUBLIC_BASE_PATH esté configurado en el entorno de Webflow Cloud con la ruta de montaje exacta, con una barra inclinada al principio y sin ella al final, y que cada solicitud fetch del cliente construya su URL a partir de ahí. Recompila después de cambiarlo, ya que los valores de NEXT_PUBLIC_ se integran durante la compilación.
La puerta de escritura deja de funcionar tras actualizar a Next.js 16
Causa: Alguien actualizó a Next.js 16 y renombró middleware.ts a proxy.ts. El archivo renombrado se ejecuta en Node, que Webflow Cloud no admite para middleware, por lo que las solicitudes PATCH llegan al controlador sin verificación de secreto.
Solución: Renombra el archivo de nuevo a middleware.ts y mantén el nombre de exportación middleware , independientemente de la versión de Next.js. Envía una solicitud PATCH sin autenticar a la ruta desplegada y espera un error 401 antes de darlo por solucionado.
Qué puedes construir a continuación con la API de inventario y Webflow
Yo añadiría un ecomm_inventory_changed webhook a continuación, para que los cambios de stock lleguen a la aplicación al instante en lugar de que la tienda tenga que consultarlos. A partir de ahí, un controlador puede combinar el stock de cada SKU con su nombre de producto y precio desde el endpoint de productos, convirtiendo el distintivo en un panel de disponibilidad completo.
Para una personalización más profunda más allá de las lecturas y escrituras de inventario, la documentación para desarrolladores de Webflow cubre el resto de la API de Ecommerce.
Preguntas frecuentes
¿Debe un trabajo de sincronización enviar quantity o updateQuantity?
Envía quantity cuando el trabajo tenga el recuento real, como en una sincronización nocturna desde un sistema de almacén, porque sobrescribe lo que Webflow tenga. Envía updateQuantity cuando el trabajo reaccione a un evento único, como una devolución que añade una unidad, porque ajusta el recuento actual sin necesidad de leerlo primero. La ruta acepta uno u otro por solicitud, nunca ambos.
¿Cuántas variables de entorno puede contener un entorno de Webflow Cloud?
La página de límites de Webflow establece un máximo de 100 variables por entorno, y esta compilación utiliza cuatro de ellas. Tanto las variables secretas como las normales cuentan para ese límite.
¿Funciona el mismo controlador de inventario en una aplicación de Astro o Vite?
El enfoque sí funciona, pero los archivos no. Webflow Cloud también acepta aplicaciones de Astro y Vite, así que traslada la ruta y la verificación secreta a la ruta del servidor de ese framework. Astro 6 y 7 requieren Node.js 22.12 o superior, un requisito menor que el 22.13.0 que necesita la CLI de Webflow.
¿Cómo puedo rotar el token del sitio?
Genera un nuevo token con los mismos alcances, actualiza en el entorno de Webflow Cloud y vuelve a desplegar para que la ruta detecte el nuevo valor. Envía una petición GET a la ruta desplegada y confirma que recibes un 200; luego, revoca el token antiguo. Dejar la revocación para el final garantiza que la ruta siga funcionando durante el cambio.WEBFLOW_SITE_TOKEN





