smplx.
Arquitectura Shopify

Estrategias de API en Shopify: REST, GraphQL y Webhooks bien utilizados [2026]

Claudio Gerlich··13 min

En nuestra Guía de Arquitectura Shopify miramos las líneas generales. Hoy profundizamos: ¿Cómo usamos correctamente las APIs de Shopify? ¿Cuándo REST, cuándo GraphQL, cómo Webhooks?

En smplx. llevamos desde 2020 construyendo integraciones entre Shopify y sistemas externos. Hemos aprendido: La estrategia de API correcta determina si tu integración es robusta o falla constantemente.

Las tres APIs de Shopify en resumen

1. Admin REST API

¿Qué es?

REST es la API clásica. Envías peticiones HTTP, recibes JSON de vuelta.

Estructura del endpoint:

POST https://myshop.myshopify.com/admin/api/2024-01/products.json

Pros:

  • Fácil de entender (HTTP, JSON)
  • Buena documentación
  • Muchos SDKs (Ruby, Node, PHP, etc.)
  • Familiar — muchos conocen REST

Contras:

  • Over-fetching: Recibes datos que no necesitas
  • Se necesitan múltiples peticiones (ej. Producto -> Variantes -> Imágenes)
  • Menos control sobre las consultas

Ejemplo: Obtener producto con variantes

# Petición 1: Producto
GET /admin/api/2024-01/products/123.json

# Petición 2: Variantes
GET /admin/api/2024-01/products/123/variants.json

# Petición 3: Imágenes
GET /admin/api/2024-01/products/123/images.json

# Total: 3 llamadas API para una info de producto "completa"

2. Admin GraphQL API

¿Qué es GraphQL?

GraphQL es un lenguaje de consultas. Pides exactamente lo que necesitas — ni más, ni menos.

Estructura de la consulta:

query GetProduct($id: ID!) {
  product(id: $id) {
    id
    title
    variants(first: 5) {
      edges {
        node {
          id
          title
          price
        }
      }
    }
    images(first: 3) {
      edges {
        node {
          url
          altText
        }
      }
    }
  }
}

Pros:

  • Sin over-fetching
  • Una petición en lugar de tres
  • Sistema de tipos (conoce todos los campos)
  • Moderno, potente
  • Mejores mensajes de error

Contras:

  • Curva de aprendizaje más pronunciada (¿qué es GraphQL?)
  • El debugging es diferente a REST
  • Los rate limits son más complejos

Ejemplo: El mismo escenario con GraphQL

const query = `
  query GetProduct($id: ID!) {
    product(id: $id) {
      id
      title
      variants(first: 5) {
        edges { node { id title price } }
      }
      images(first: 3) {
        edges { node { url altText } }
      }
    }
  }
`;

const response = await fetch(shopifyEndpoint, {
  method: 'POST',
  headers: { 'X-Shopify-Access-Token': token },
  body: JSON.stringify({ query, variables: { id: 'gid://shopify/Product/123' } })
});

// ¡Todo en una sola petición!

3. Storefront API

¿Qué es la Storefront API?

La Storefront API es GraphQL, pero para tu cliente (no admin). Es pública (con scope limitado).

Diferencias con Admin GraphQL:

  • No puede escribir (solo lectura)
  • Menos información (ej. sin detalles de inventario para otras variantes)
  • Diseñada para escenarios de cliente (carrito, checkout)
  • Los rate limits son diferentes (más generosos para solo lectura)

¿Cuándo usarla?

  • Tu frontend (tienda headless)
  • Sitios web externos (integrar resultados de búsqueda de productos)
  • Apps móviles

Webhooks: Arquitectura basada en eventos

¿Qué son los Webhooks?

En lugar de consultar constantemente la API ("¿Hay nuevos pedidos?"), Shopify te dice: "¡Atención, un nuevo pedido!" — vía webhook.

Arquitectura:

Cliente compra -> Shopify llama a tu backend (HTTP POST) -> Tu código reacciona

Ejemplo de webhook: Pedido creado

Tu endpoint de webhook:

const express = require('express');
const app = express();

// Validar webhook (¡importante!)
const verifyShopifyWebhook = (req, shopifySecret) => {
  const hmac = req.headers['x-shopify-hmac-sha256'];
  const body = req.rawBody;

  const hash = crypto
    .createHmac('sha256', shopifySecret)
    .update(body, 'utf8')
    .digest('base64');

  return hash === hmac;
};

app.post('/webhooks/orders/create', (req, res) => {
  if (!verifyShopifyWebhook(req, process.env.SHOPIFY_API_SECRET)) {
    return res.status(401).send('Unauthorized');
  }

  const order = req.body;

  // Tu lógica aquí
  console.log(`Order #${order.order_number} creado`);

  // Enviar al ERP
  sendToERP(order);

  // Enviar email de confirmación
  sendConfirmationEmail(order.customer);

  res.status(200).send('OK');
});

app.listen(3000);

Seguridad importante de webhooks:

  1. Valida la firma (ver código arriba)
  2. Haz tus endpoints idempotentes (el webhook podría llegar 2 veces)
  3. Responde rápido (Shopify espera solo 30 segundos)

Ejemplo de idempotencia

app.post('/webhooks/orders/create', async (req, res) => {
  const order = req.body;
  const webhookId = req.headers['x-shopify-webhook-id'];

  // Verificar: ¿Ya procesamos este webhook?
  const existing = await db.webhooks.findOne({
    shopify_webhook_id: webhookId
  });

  if (existing) {
    console.log('Webhook ya procesado');
    return res.status(200).send('OK');
  }

  // Nuevo -> Procesar
  await processOrder(order);

  // Marcar como procesado
  await db.webhooks.insert({
    shopify_webhook_id: webhookId,
    processed_at: new Date()
  });

  res.status(200).send('OK');
});

Importante: Necesitas idempotencia porque los webhooks pueden llegar múltiples veces.

Rate Limits: Cómo manejarlos

Rate Limits de la REST API

Cuota: 2 llamadas API por segundo (plan estándar)

const rateLimiter = new RateLimiter({
  maxRequests: 2,
  interval: 1000 // 1 segundo
});

const makeRequest = async (path) => {
  await rateLimiter.acquire();

  return fetch(`https://myshop.myshopify.com/admin/api/2024-01/${path}`, {
    headers: { 'X-Shopify-Access-Token': token }
  });
};

Rate Limits de GraphQL

Más complejo: Basado en la complejidad de la consulta, no solo en el conteo.

Cabecera de Shopify después de la petición:

X-Shopify-GraphQL-Bulkoperation-Resource-State: running
Graphql-Cost: { "requestedQueryCost": 25, "actualQueryCost": 10, "throttleStatus": { "maximumAvailable": 1000, "currentlyAvailable": 990, "restoreRate": 50 } }

Qué significa:

  • Tienes 1000 "puntos"
  • Esta consulta costó 10 puntos
  • Regeneras 50 puntos por segundo
  • Si < 100 puntos: ¡Throttle!
const checkRateLimit = (response) => {
  const cost = JSON.parse(response.headers['graphql-cost']);

  if (cost.throttleStatus.currentlyAvailable < 100) {
    console.log('¡Rate Limit crítico! Esperando...');
    const waitTime = (100 - cost.throttleStatus.currentlyAvailable) / cost.throttleStatus.restoreRate * 1000;
    return new Promise(resolve => setTimeout(resolve, waitTime));
  }
};

Caso real: Caso de estudio helpyourself

helpyourself es una tienda de comercio médico que necesita sincronizar Shopify con su ERP (iLabServer).

Desafío:

  • 10.000+ productos
  • El inventario debe sincronizarse en tiempo real
  • Crítico para RGPD (datos de clientes)
  • Los errores no pueden ocurrir (¡dispositivos médicos!)

Solución: Webhook-Driven con lógica de reintentos

// 1. Shopify Webhook -> Pedido creado
app.post('/webhooks/orders/create', async (req, res) => {
  const order = req.body;

  // 2. Inmediato: Enviar a iLabServer (¡los webhooks deben ser rápidos!)
  const jobId = await queue.add('sync-order-to-erp', {
    orderId: order.id,
    attempt: 1
  }, {
    priority: 'high',
    attempts: 5, // Hasta 5 intentos
    backoff: { type: 'exponential', delay: 2000 } // 2s, 4s, 8s, etc.
  });

  res.status(202).send({ jobId });
});

// 3. Background Worker (Bull Queue)
queue.process('sync-order-to-erp', async (job) => {
  const { orderId, attempt } = job.data;

  try {
    const order = await shopify.get(`/admin/api/2024-01/orders/${orderId}.json`);
    const response = await iLabServer.post('/api/orders', {
      order_number: order.order_number,
      customer: order.customer,
      items: order.line_items,
      total: order.total_price
    });

    if (!response.ok) {
      throw new Error(`iLabServer returned ${response.status}`);
    }

    await logSuccess(orderId);
  } catch (error) {
    console.error(`Intento ${attempt} falló:`, error);
    throw error; // Bull activará reintento
  }
});

Por qué los webhooks son brillantes aquí:

  • Asíncronos (los clientes no ven el retraso)
  • Fiables con lógica de reintentos
  • Escalables (muchos pedidos en paralelo)
  • Compatibles con RGPD (los datos no pasan por el frontend)

REST vs. GraphQL: ¿Cuándo cuál?

Escenario REST GraphQL
Obtención de datos puntual Si Si
Operaciones en lote (1000+ pedidos) No Si
Escritura (editar productos) Si No (no en Admin)
Consultas complejas (3+ peticiones) No Si
Rendimiento crítico No Si
Manejo de rate limits importante Cuidado Si
El equipo no conoce GraphQL Si Cuidado

Nuestra regla: GraphQL para consultas complejas, REST para operaciones CRUD simples.

Errores que vemos

Error 1: Sin lógica de reintentos en webhooks

// INCORRECTO
app.post('/webhooks/orders/create', async (req, res) => {
  const result = await externalAPI.post(order);
  // ¿Qué pasa si externalAPI está caída? ¡El pedido se pierde!
});

// CORRECTO
app.post('/webhooks/orders/create', async (req, res) => {
  // Responder inmediatamente
  res.status(202).send('OK');

  // Luego procesar async con reintento
  await queue.add('process-order', order, { attempts: 5 });
});

Error 2: Sin validación de firma del webhook

// INCORRECTO: Confía en cualquier POST
app.post('/webhooks/orders/create', (req, res) => {
  processOrder(req.body); // ¡Cualquiera puede activar esto!
});

// CORRECTO
app.post('/webhooks/orders/create', (req, res) => {
  if (!verifySignature(req)) {
    return res.status(401).send('Unauthorized');
  }
  processOrder(req.body);
});

Error 3: Sin manejo de idempotencia

// INCORRECTO
app.post('/webhooks/orders/create', async (req, res) => {
  const order = req.body;
  // ¿Shopify lo envía 2 veces? ¡Dos pedidos en el ERP!
  await erp.createOrder(order);
});

// CORRECTO
app.post('/webhooks/orders/create', async (req, res) => {
  const webhookId = req.headers['x-shopify-webhook-id'];

  if (await alreadyProcessed(webhookId)) {
    return res.status(200).send('OK');
  }

  await erp.createOrder(req.body);
  await markProcessed(webhookId);
});

Mejores prácticas para integraciones robustas

1. Event-Driven (Webhooks) en lugar de Polling

// INCORRECTO: Consultar cada 10 segundos
setInterval(async () => {
  const orders = await shopify.get('/admin/api/2024-01/orders.json');
  // Costoso en API y retraso de datos
}, 10000);

// CORRECTO: Webhooks
shopify.webhookSubscribe('orders/create', myWebhookEndpoint);

2. Siempre Logging/Monitoreo

const logger = require('winston');

app.post('/webhooks/orders/create', async (req, res) => {
  logger.info('Webhook recibido', {
    webhook_id: req.headers['x-shopify-webhook-id'],
    order_id: req.body.id
  });

  try {
    await processOrder(req.body);
    logger.info('Pedido procesado correctamente');
  } catch (error) {
    logger.error('Procesamiento de pedido falló', { error: error.message });
    throw error; // La cola hará reintento
  }
});

3. Construir Health-Checks

app.get('/health', async (req, res) => {
  const shopifyReachable = await checkShopifyConnection();
  const erpReachable = await checkERPConnection();

  if (shopifyReachable && erpReachable) {
    res.status(200).send('OK');
  } else {
    res.status(503).send('Service Unavailable');
  }
});

4. Gestionar rate limits proactivamente

const monitorRateLimit = async () => {
  const response = await makeGraphQLQuery(testQuery);
  const { throttleStatus } = JSON.parse(
    response.headers['graphql-cost']
  );

  if (throttleStatus.currentlyAvailable < 200) {
    logger.warn('Rate limit crítico', {
      available: throttleStatus.currentlyAvailable
    });
    // Podría activar lógica de negocio aquí (ej. pausar trabajo en lote)
  }
};

Resumen: La estrategia de API para 2026

  1. REST para operaciones simples (cambiar un producto, leer un pedido)
  2. GraphQL para consultas complejas (muchos datos relacionados con una sola petición)
  3. Webhooks para arquitectura basada en eventos (siempre que sea posible)
  4. Storefront API para orientado al cliente (tiendas headless, apps móviles)

La combinación de las tres es a menudo la mejor solución.


Sobre el autor

Claudio Gerlich es Technical Architect en smplx. y está especializado en integraciones Shopify desde 2020. Con smplx. hemos implementado escenarios de sincronización complejos con ERPs, CRMs y sistemas especializados — siempre con un enfoque en fiabilidad y manejo de errores.

helpyourself es un buen ejemplo de una integración robusta: Fiabilidad de grado médico con webhooks y lógica de reintentos.

smplx. es Shopify Technical Partner (desde 2020) con sede en Muensterland, NRW.