Cómo conectar Printful a Webflow Ecommerce con Webflow Cloud

Cómo conectar Printful a Webflow Ecommerce y recibir eventos de pedidos con Webflow Cloud

Aprende a crear una aplicación de Webflow Cloud que envíe los pedidos de Webflow a Printful y los marque como completados cuando se realicen los envíos.

Cómo conectar Printful a Webflow Ecommerce y recibir eventos de pedidos con Webflow Cloud

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

Printful ya no se sincroniza automáticamente con Webflow, por lo que una pequeña aplicación de Webflow Cloud puede encargarse de llevar cada pedido a Printful y cada envío de vuelta.

Printful finalizó su integración con Webflow el 3 de marzo de 2025, por lo que los pedidos de una tienda de Webflow Ecommerce ya no llegan a Printful por sí solos. La página de integración de Printful de Webflow ahora redirige a herramientas de automatización o a una solución personalizada, y esta guía te enseña a seguir el camino personalizado.

En esta solución, una pequeña aplicación de Webflow Cloud recibe el webhook de nuevo pedido de Webflow y crea el pedido correspondiente en Printful a través de su API. Cuando Printful envía el paquete, la misma aplicación añade el número de seguimiento al pedido de Webflow y lo marca como completado, para que el cliente reciba el correo electrónico de envío de Webflow.

¿Qué necesitas para conectar Printful a Webflow Ecommerce en Webflow Cloud?

Necesitas cinco cosas:

  • Sitio de Webflow Ecommerce: Un sitio con un plan de Ecommerce que contenga los productos que vas a imprimir, y un token de sitio con los alcances ecommerce:read, ecommerce:write y sites:write.
  • Cuenta de Printful: Una tienda de pedidos manuales/API de Printful, el tipo de tienda que ofrece Printful para integraciones mediante API, con tus productos y sus diseños ya creados.
  • Token privado de Printful: Creado en el portal de desarrolladores de Printful para esa tienda, de modo que las solicitudes no necesiten un encabezado de ID de tienda independiente.
  • Node.js 22, Next.js 15 y npm: La página bring-your-own-app establece los requisitos mínimos y señala: "Actualmente, Webflow Cloud solo admite el gestor de paquetes npm".
  • Un token aleatorio para Printful: Un valor largo para la URL del webhook de Printful, generado con openssl rand -hex 32. El secreto del webhook de Webflow llegará más tarde, cuando registres ese webhook en el paso 5.

6 pasos para conectar Printful a Webflow Ecommerce en Webflow Cloud

La solución es una aplicación de Next.js con dos Route Handlers. Uno recibe los pedidos de Webflow y los crea en Printful, y el otro recibe los envíos de Printful y completa el pedido en Webflow.

1. Haz coincidir cada SKU de Webflow con su ID de variante de sincronización de Printful

Printful identifica cada variante de producto que vendes, como una camiseta de una talla y color específicos, mediante un ID de variante de sincronización. Webflow identifica la misma variante por su SKU. La ruta de pedidos convierte uno en otro leyendo el SKU como el ID de Printful, por lo que el campo SKU de cada variante en Webflow debe contener exactamente ese número.

Enumera tus productos de Printful y luego lee las variantes de cada uno con tu token de Printful:

curl -s https://api.printful.com/store/products \
  -H "Authorization: Bearer $PRINTFUL_API_TOKEN" | jq '.result[] | {id, name}'

curl -s https://api.printful.com/store/products/PRODUCT_ID \
  -H "Authorization: Bearer $PRINTFUL_API_TOKEN" | jq '.result.sync_variants[] | {id, name}'

Crea cada producto en Webflow con sus variantes correspondientes, ya sea manualmente en el Designer o mediante una importación, y pega cada id ID de variante de sincronización en el campo SKU de la variante de Webflow correspondiente. Una variante cuyo SKU no sea un ID de Printful no podrá completarse, y la ruta de pedidos registrará el error por su nombre.

Al finalizar este paso, cada variante imprimible de Webflow tendrá un SKU numérico que coincide con una variante de sincronización en Printful.

2. Prepara la estructura de la aplicación y configura las variables de entorno

Crea la aplicación con npm y fija Next.js 15, la versión que admite Webflow Cloud:

npx create-next-app@15 printful-bridge --typescript --app
cd printful-bridge

Deja <aux translate="no">next.config.js</aux> tranquilo; la next.configdocumentación de configuración de Webflow dice: "No necesitas añadir un adaptador, una ruta base ni un modo de salida". No añadas <aux translate="no">export const runtime = 'edge'</aux> a ninguna de las dos rutas.export const runtime = 'edge'

La página de "bring-your-own-app" todavía lo sugiere para las rutas de API, pero el adaptador OpenNext Cloudflare que utiliza Webflow Cloud no es compatible con ese destino de tiempo de ejecución de Next.js, y la compilación falla.

La aplicación lee seis variables, configuradas en <aux translate="no">.env.local</aux> localmente y en el entorno de Webflow Cloud para el despliegue:.env.local

  • <aux translate="no">WEBFLOW_SITE_ID</aux> y <aux translate="no">WEBFLOW_SITE_TOKEN</aux>: El ID del sitio desde la configuración del sitio y el token del sitio desde los requisitos previos; marca el token como secreto.
  • <aux translate="no">WEBFLOW_WEBHOOK_SECRET</aux>: El <aux translate="no">secret</aux> que Webflow devuelve en el paso 5; márcalo como secreto y déjalo vacío hasta entonces.secretKey
  • <aux translate="no">PRINTFUL_API_TOKEN</aux>: El token privado de Printful; márcalo como secreto.
  • <aux translate="no">PRINTFUL_WEBHOOK_TOKEN</aux>: La cadena aleatoria que Printful enviará de vuelta en la URL del webhook; márcala como secreto.
  • <aux translate="no">PRINTFUL_AUTO_CONFIRM</aux>: Configúralo en <aux translate="no">true</aux> para que Printful comience a procesar los pedidos de inmediato, o déjalo sin configurar para crear borradores que tú confirmes en el panel de control de Printful.true

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 valores secretos se ocultan en los registros de compilación.

3. Escribe la ruta de pedido que envía los pedidos de Webflow a Printful

Webflow firma los webhooks creados a través de su API con dos encabezados: <aux translate="no">x-webflow-timestamp</aux> en milisegundos, y <aux translate="no">x-webflow-signature</aux>, un HMAC SHA256 hexadecimal de la marca de tiempo, dos puntos y el cuerpo sin procesar.x-webflow-timestampx-webflow-signature

La comprobación reside en su propio módulo para que se lea de forma limpia.

Crea :lib/webflow-signature.ts

// lib/webflow-signature.ts
const MAX_AGE_MS = 5 * 60 * 1000;
const encoder = new TextEncoder();

async function hmacHex(secret: string, message: string): Promise<string> {
  const key = await crypto.subtle.importKey(
    'raw',
    encoder.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(message));
  return Array.from(new Uint8Array(signature))
    .map((byte) => byte.toString(16).padStart(2, '0'))
    .join('');
}

function safeEqual(a: string, b: string): boolean {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return diff === 0;
}

export async function isSignedByWebflow(request: Request, rawBody: string, secret: string) {
  const timestamp = request.headers.get('x-webflow-timestamp') ?? '';
  const signature = request.headers.get('x-webflow-signature') ?? '';
  const sentAt = Number(timestamp);
  if (!timestamp || !Number.isFinite(sentAt) || Math.abs(Date.now() - sentAt) > MAX_AGE_MS) {
    return false;
  }
  return safeEqual(signature, await hmacHex(secret, `${timestamp}:${rawBody}`));
}

La ruta de pedido verifica la firma, asigna el pedido de Webflow a un pedido de Printful y lo crea con la API de pedidos de Printful.

Implementación del controlador de ruta

Crea :app/api/webflow/order/route.ts

// app/api/webflow/order/route.ts
import { NextResponse } from 'next/server';
import { isSignedByWebflow } from '@/lib/webflow-signature';

type Address = {
  addressee?: string;
  line1?: string;
  line2?: string;
  city?: string;
  state?: string;
  country?: string;
  postalCode?: string;
};

type WebflowOrder = {
  orderId: string;
  customerInfo?: { email?: string };
  shippingAddress?: Address;
  purchasedItems?: { count: number; variantSKU?: string; variantName?: string }[];
};

export async function POST(request: Request) {
  const secret = process.env.WEBFLOW_WEBHOOK_SECRET;
  const printfulToken = process.env.PRINTFUL_API_TOKEN;
  if (!secret || !printfulToken) {
    return NextResponse.json({ error: 'Route is not configured' }, { status: 500 });
  }

  const rawBody = await request.text();
  if (!(await isSignedByWebflow(request, rawBody, secret))) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const event = JSON.parse(rawBody) as { triggerType?: string; payload?: WebflowOrder };
  const order = event.payload;
  if (event.triggerType !== 'ecomm_new_order' || !order?.orderId) {
    return NextResponse.json({ ignored: true });
  }

  const items = (order.purchasedItems ?? []).map((item) => ({
    sync_variant_id: Number(item.variantSKU),
    quantity: item.count,
    name: item.variantName,
  }));
  const unmapped = items.filter((item) => !Number.isInteger(item.sync_variant_id));
  if (unmapped.length > 0 || items.length === 0) {
    // A mapping error will not fix itself on retry, so log it and answer 200.
    console.error('Order has items without a Printful SKU', order.orderId, unmapped);
    return NextResponse.json({ ok: false, reason: 'unmapped items' });
  }

  const address = order.shippingAddress ?? {};
  const state = address.state ?? '';
  const recipient = {
    name: address.addressee,
    address1: address.line1,
    address2: address.line2,
    city: address.city,
    country_code: address.country,
    zip: address.postalCode,
    email: order.customerInfo?.email,
    ...(state.length <= 3 ? { state_code: state } : { state_name: state }),
  };

  const confirm = process.env.PRINTFUL_AUTO_CONFIRM === 'true' ? '?confirm=1' : '';
  let response: Response;
  try {
    response = await fetch(`https://api.printful.com/orders${confirm}`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${printfulToken}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        external_id: `webflow-${order.orderId}`,
        recipient,
        items: items.map(({ sync_variant_id, quantity }) => ({ sync_variant_id, quantity })),
      }),
      signal: AbortSignal.timeout(10000),
    });
  } catch {
    // A network failure is worth a retry, so answer non-200 and let Webflow resend.
    return NextResponse.json({ error: 'Printful unreachable' }, { status: 502 });
  }

  const result = await response.text();
  if (response.status >= 500) {
    return NextResponse.json({ error: 'Printful error' }, { status: 502 });
  }
  if (!response.ok) {
    console.error('Printful rejected order', order.orderId, response.status, result);
    return NextResponse.json({ ok: false, status: response.status });
  }

  return NextResponse.json({ ok: true });
}

La ruta responde a Webflow con un 200 ante cualquier problema que un reintento no pueda solucionar, como un SKU no asignado o una dirección que Printful rechaza, y en su lugar lo registra. Esto es importante porque Webflow reintenta cualquier respuesta que no sea 200 hasta tres veces más y desactiva un webhook que sigue fallando, por lo que un solo pedido erróneo no debería desactivar la entrega de pedidos para toda la tienda.

Los fallos de red y los errores del servidor de Printful reciben un 502, y el reintento de Webflow es la respuesta correcta para ellos. Enviar el ID de pedido de Webflow como <aux translate="no">external_id</aux> permite que Printful rechace una segunda copia del mismo pedido si un reintento llega después de un éxito.external_id

Printful acepta un código de estado de dos letras, y Webflow a veces envía el nombre completo del estado, por lo que la ruta pasa la forma que reciba en el campo correspondiente de Printful.

4. Escribe la ruta de envío que marca los pedidos de Webflow como cumplidos

Cuando Printful envía un pedido, envía un webhook <aux translate="no">package.shipped</aux> con el transportista y el número de seguimiento. Los webhooks v1 de Printful no están firmados, por lo que la ruta comprueba un token secreto en su propia URL.package_shipped

Lee el ID de pedido de Webflow desde external_id, añade los detalles de seguimiento a través del endpoint update order de Webflow y, a continuación, marca el pedido como completado, lo que envía al cliente el correo electrónico de pedido completado de Webflow.

Crea :app/api/printful/shipped/route.ts

// app/api/printful/shipped/route.ts
import { NextResponse } from 'next/server';

type ShippedEvent = {
  type?: string;
  data?: {
    shipment?: { carrier?: string; service?: string; tracking_number?: string; tracking_url?: string };
    order?: { external_id?: string };
  };
};

async function webflow(path: string, method: string, body: unknown) {
  return fetch(`https://api.webflow.com/v2/sites/${process.env.WEBFLOW_SITE_ID}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${process.env.WEBFLOW_SITE_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(8000),
  });
}

export async function POST(request: Request) {
  const expected = process.env.PRINTFUL_WEBHOOK_TOKEN;
  const token = new URL(request.url).searchParams.get('token') ?? '';
  if (!expected || token !== expected) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }

  const event = (await request.json().catch(() => null)) as ShippedEvent | null;
  const externalId = event?.data?.order?.external_id ?? '';
  if (event?.type !== 'package_shipped' || !externalId.startsWith('webflow-')) {
    return NextResponse.json({ ignored: true });
  }

  const orderId = externalId.slice('webflow-'.length);
  const shipment = event.data?.shipment ?? {};

  try {
    const tracking = await webflow(`/orders/${orderId}`, 'PATCH', {
      shippingProvider: [shipment.carrier, shipment.service].filter(Boolean).join(' '),
      shippingTracking: shipment.tracking_number,
      shippingTrackingURL: shipment.tracking_url,
    });
    const fulfilled = await webflow(`/orders/${orderId}/fulfill`, 'POST', {
      sendOrderFulfilledEmail: true,
    });
    if (!tracking.ok || !fulfilled.ok) {
      console.error('Webflow update failed', orderId, tracking.status, fulfilled.status);
      return NextResponse.json({ error: 'Webflow update failed' }, { status: 502 });
    }
  } catch {
    return NextResponse.json({ error: 'Webflow unreachable' }, { status: 502 });
  }

  return NextResponse.json({ ok: true });
}

Aquí, una respuesta distinta a 200 es deliberada ante cualquier fallo, ya que Printful reintenta un webhook que falla y los errores de la API de Webflow suelen ser breves. La actualización del seguimiento se ejecuta antes de la llamada de cumplimiento para que el correo electrónico de pedido completado pueda incluir el enlace de seguimiento.

La comparación del token de la URL es una simple comprobación de igualdad, lo cual es suficiente para un valor aleatorio largo que solo conocen Printful y tu entorno.

5. Despliega la aplicación y registra ambos webhooks

Despliega la aplicación en Webflow Cloud y móntala, por ejemplo, en /printful, luego registra los dos webhooks en las rutas desplegadas. Ambos registros son llamadas a la API; ninguno tiene un formulario en el panel de control que funcione para esta compilación.

Registra el webhook de nuevo pedido de Webflow a través de la API de datos, porque Webflow solo firma los webhooks creados allí:

curl -X POST "https://api.webflow.com/v2/sites/$WEBFLOW_SITE_ID/webhooks" \
  -H "Authorization: Bearer $WEBFLOW_SITE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"triggerType":"ecomm_new_order","url":"https://www.example.com/printful/api/webflow/order"}'

La respuesta incluye un secretKey, que se muestra solo una vez. Guárdalo como WEBFLOW_WEBHOOK_SECRET en el entorno de Webflow Cloud y envía un commit para que el despliegue lo detecte.

Luego registra el webhook de envío con Printful:

curl -X POST https://api.printful.com/webhooks \
  -H "Authorization: Bearer $PRINTFUL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.example.com/printful/api/printful/shipped?token=YOUR_PRINTFUL_WEBHOOK_TOKEN","types":["package_shipped"]}'

Printful permite una URL de webhook por tienda en la v1, y esta llamada reemplaza cualquier configuración que la tienda ya tuviera, así que consulta primero con GET https://api.printful.com/webhooks si otra herramienta utiliza la tienda. Ambas URLs incluyen la ruta de montaje, porque cada ruta reside bajo ella.

6. Realiza un pedido de prueba de principio a fin

Con PRINTFUL_AUTO_CONFIRM sin establecer, realiza un pedido real en la tienda publicada usando un pago de prueba. En pocos segundos, el pedido debería aparecer en el panel de control de Printful como un borrador con los artículos correspondientes y la dirección del cliente, y los registros de Webflow Cloud no deberían mostrar errores en la ruta del pedido.

Un borrador no cuesta nada, porque Printful solo cobra por los pedidos confirmados.

Para probar la ruta de retorno sin enviar nada, envía a la ruta de envío el formato que envía Printful, utilizando un ID de pedido de Webflow real:

curl -X POST "https://www.example.com/printful/api/printful/shipped?token=YOUR_PRINTFUL_WEBHOOK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type":"package_shipped","data":{"order":{"external_id":"webflow-ORDER_ID"},"shipment":{"carrier":"USPS","service":"Ground","tracking_number":"TEST123","tracking_url":"https://example.com/track/TEST123"}}}'

Una respuesta de {"ok":true} y un pedido completado con detalles de seguimiento en la vista de Pedidos de Webflow significan que ambas direcciones funcionan. Elimina el borrador de prueba en Printful, luego establece PRINTFUL_AUTO_CONFIRM en true cuando estés listo para que los pedidos vayan directamente a producción.

¿Qué causa que la integración entre Printful y Webflow Cloud falle?

La mayoría de los fallos provienen del mapeo de SKU o de los registros de webhooks; el resto proviene de la compilación y el entorno de Webflow Cloud. Cada entrada comienza a partir de lo que muestran los registros o los paneles de control.

Un pedido de Webflow nunca aparece en Printful

Causa: La ruta del pedido registró el pedido en lugar de enviarlo. "El pedido tiene artículos sin un SKU de Printful" significa que el SKU de una variante no es un ID de variante de sincronización numérico. "Printful rechazó el pedido" significa que Printful rechazó la solicitud, la mayoría de las veces por una dirección que no pudo validar o una variante de sincronización que fue eliminada en Printful.

Si los registros no muestran nada, el webhook se creó en el panel de control, por lo que llegó sin firmar y la ruta devolvió un 401, o nunca se registró.

Solución: Corrige el SKU en Webflow o la dirección en Printful, y crea el pedido faltante manualmente para ese cliente. Para un webhook faltante o sin firmar, enumera los webhooks del sitio con GET /v2/sites/{site_id}/webhooks, elimina cualquier copia del panel de control y regístralo de nuevo como en el paso 5 con el nuevo secretKey guardado.

Los pedidos llegan a Printful pero nunca se marcan como completados

Causa: Falta el webhook de envío, apunta a una URL sin la ruta de montaje o el token, o fue reemplazado cuando otra herramienta registró su propio webhook en la misma tienda de Printful. Un error 502 de la ruta en el historial de reintentos de Printful apunta en su lugar al token del sitio, que necesita el ámbito.ecommerce:write

Solución: Ejecuta y compara la URL con la ruta desplegada, incluyendo la ruta de montaje y . Regístralo de nuevo si difiere, y regenera el token del sitio de Webflow con si la ruta informa de una actualización fallida.GET https://api.printful.com/webhooks?token=ecommerce:write

La compilación de Webflow Cloud falla en una ruta que funciona con next dev

Causa: Una ruta declara , lo cual no es compatible con el adaptador de OpenNext Cloudflare, o una actualización a Next.js 16 renombró el middleware a .export const runtime = 'edge'proxy.ts

La documentación de personalización del framework de Webflow indica que "el middleware de tiempo de ejecución de Node.js no es compatible. Solo el middleware de tiempo de ejecución Edge funciona en el tiempo de ejecución de Workers".

Solución: Elimina la directiva de cada ruta y vuelve a desplegar; las rutas ya se ejecutan en Workers sin ella. Mantén cualquier archivo de middleware llamado , independientemente de la versión de Next.js.middleware.ts

Todos los webhooks devuelven un error 500 después del despliegue

Causa: Existe una variable en pero no en el entorno de Webflow Cloud, por lo que la comprobación de configuración de la ruta falla antes de leer la solicitud..env.local

Solución: Añade la variable que falta en el entorno y envía un commit para que un nuevo despliegue la detecte. Los secretos se ocultan en los registros de compilación, así que confirma un valor pegándolo de nuevo en lugar de leer el registro.

Qué puedes construir a continuación con Printful y Webflow

El mismo puente puede escuchar los eventos de actualización de stock y productos de Printful y mantener sincronizado el inventario de Webflow, utilizando el endpoint de inventario de Webflow para cada SKU. Ambas direcciones dependen de webhooks firmados, y la guía de Webflow sobre seguridad de webhooks explica contra qué protege la verificación de firma.

Para obtener el objeto de pedido completo y los demás endpoints de pedidos, consulta la documentación para desarrolladores de Webflow.

Preguntas frecuentes

¿Esto me cobra por cada pedido de inmediato?

Solo si es . Printful cobra por los pedidos una vez que se confirman, así que con la variable desactivada, cada pedido queda como borrador hasta que alguien lo confirme en el panel de control de Printful. Ejecutar borradores durante los primeros días permite detectar errores de mapeo antes de que se mueva dinero.PRINTFUL_AUTO_CONFIRMtrue

¿Qué sucede si Printful no está disponible cuando llega un pedido?

La ruta del pedido responde con un 502 y Webflow reintenta el webhook hasta tres veces más, con diez minutos de diferencia. Si Printful sigue sin estar disponible después de eso, el pedido permanece en la vista de Pedidos de Webflow y puedes crearlo manualmente en Printful. Los fallos repetidos pueden desactivar el webhook de Webflow, así que comprueba su estado después de cualquier interrupción prolongada de Printful.

¿Puedo seguir usando las maquetas y precios de productos de Printful en Webflow?

No automáticamente. Las maquetas y los precios residen en Printful, y nada los copia a Webflow ahora que la integración nativa ha desaparecido. Sube las maquetas como imágenes de producto y establece los precios en Webflow al crear cada producto, y mantén el SKU igual al ID de variante de sincronización para que los pedidos sigan mapeándose correctamente.


Ú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.