Todos los artículos
Shopifywebhook debugginglocal testinge-commerce

Prueba webhooks de Shopify en local sin sorpresas

La mayoría de los bugs de webhooks de Shopify en desarrollo local se reducen a una línea: el HMAC va codificado en base64, no en hex. Si llamas a .digest('hex') y comparas con X-Shopify-Hmac-Sha256, la verificación nunca pasará — incluso con el secreto compartido correcto. Hemos visto a ingenieros senior perder una tarde por ese carácter.

La trampa del base64

Stripe usa hex. GitHub usa hex (con prefijo sha256=). Shopify usa base64. Tres proveedores, tres codificaciones. Tu primera versión del verificador casi seguro copiará un snippet de otro proyecto y fallará en silencio.

Así debería verse la verificación:

const hmac = crypto
  .createHmac('sha256', SHOPIFY_API_SECRET)
  .update(rawBody)         // raw body, not JSON-parsed
  .digest('base64');       // base64, not hex

const valid = crypto.timingSafeEqual(
  Buffer.from(hmac),
  Buffer.from(req.headers['x-shopify-hmac-sha256']),
);

Dos cosas importan más allá de la codificación. El cuerpo tienen que ser los bytes crudos, sin parsear. Y la comparación debe ser de tiempo constante — la igualdad de cadenas filtra información byte a byte.

Por qué un túnel lo hace más fácil

También puedes usar el comando shopify app dev, que levanta un túnel interno. Funciona. Pero también envuelve tu servidor de desarrollo en su propio proceso, traga logs de cierta forma y no te da botón de reenvío. Para desarrollo de apps más allá de un "hola mundo", una URL pública estable más un túnel localhost con captura de peticiones ahorra más tiempo del que la CLI ahorra en la configuración inicial.

npx portpreview 3000

Pegas la URL HTTPS en la configuración de webhooks de tu app en el panel de Partners, disparas un evento desde una tienda de prueba, y la petición aterriza en tu handler local con todas las cabeceras intactas.

Tiendas de prueba: la parte que la documentación de Shopify pasa por encima

Dos cosas que saber antes de cablear nada:

  • Las tiendas de desarrollo disparan todos los eventos de webhook. Creación de pedido, fulfillment, inventario — todo. No necesitas falsear nada; solo instala tu app en una tienda de desarrollo y haz clic.
  • El secreto del webhook difiere por app y por canal de entrega. Si también te suscribes a EventBridge o Pub/Sub, el comportamiento del HMAC es distinto. Aquí hablamos de entrega de webhook HTTPS plana.

Configuración paso a paso

  1. Arranca tu app localmente en el puerto que uses (3000 es común).
  2. Ejecuta npx portpreview 3000 y copia la URL HTTPS que imprime.
  3. En el panel de Partners de Shopify, abre tu app y ve a Configuration → Webhooks.
  4. Pon el endpoint del webhook en https://your-tunnel.portpreview.dev/api/webhooks/shopify (o el path que use tu app).
  5. Instala la app en una tienda de desarrollo, luego dispara un evento — crea un pedido borrador, cumple un artículo, cambia el inventario.
  6. Mira aterrizar la petición. Inspecciona cabeceras y cuerpo. Reenvía la petición capturada tras cada arreglo del handler.

Los errores que seguimos viendo

Cuerpo parseado antes de verificar

Si pones app.use(express.json()) antes de tu ruta de webhook, los bytes crudos ya no están cuando intentas verificar. Monta un parser de cuerpo crudo solo en el path del webhook, o saca el cuerpo manualmente del stream de la petición antes de cualquier parsing JSON.

Secretos confundidos

El secreto de la app de Partners no es el mismo que el token de la Storefront API. El HMAC del webhook usa el secreto de la app. Si estás mirando shp_xxx en tu archivo env, agarraste el equivocado.

Eventos de prueba que parecen idénticos

El botón "Enviar notificación de prueba" de Shopify entrega un evento sintético con cuerpo de marcador. La firma de ese evento de prueba es real, pero el payload es fijo. Para pruebas realistas de forma de payload, dispara eventos desde la propia tienda de desarrollo.

El reenvío no es negociable para el desarrollo de apps

Shopify reintenta webhooks fallidos hasta 48 horas con backoff exponencial. Es generoso, pero cuando iteras, no quieres esperar al siguiente reintento. Captura la primera entrega en el historial de peticiones de tu túnel y reenvíala a demanda contra tu handler local.

Cuándo lo local deja de bastar

Los flujos de registro de tienda, los webhooks de GDPR y los cambios de facturación de suscripciones son más fáciles de validar contra un entorno desplegado porque interactúan con el propio estado de cuenta de Shopify. Para todo lo demás — parsing de payload, verificación de firma, lógica de negocio — local es más rápido.

Para la matemática de firma subyacente en todos los proveedores, lee nuestra guía de verificación de firma de webhooks. Únete a la lista de espera de PortPreview si quieres un túnel con reenvío integrado.

Preguntas frecuentes

¿Cómo pruebo webhooks de Shopify en localhost?
Inicia un túnel como PortPreview, obtén una URL HTTPS pública y regístrala como endpoint de webhook en el panel de Partners de Shopify. Instala tu app en una tienda de desarrollo, dispara eventos, y las entregas llegan a tu handler local.
¿Por qué mi verificación HMAC de Shopify siempre falla?
La mayoría de las veces es la codificación. Shopify firma con HMAC SHA-256 y codifica el resultado en base64. Si tu código usa .digest('hex') en vez de .digest('base64'), toda comparación falla, aun con el secreto correcto.
¿Necesito shopify app dev o puedo usar cualquier túnel?
Cualquier túnel funciona. shopify app dev incluye su propio túnel por conveniencia, pero un túnel de propósito general con captura y reenvío de peticiones es más útil para el desarrollo sostenido.