smplx.
Shopify Architektur

Shopify API-Strategien: REST, GraphQL und Webhooks richtig nutzen [2026]

Claudio Gerlich··13 min

Im Shopify-Architektur-Ratgeber schauen wir auf die großen Linien. Heute greifen wir tiefer: Wie nutzen wir Shopify's APIs richtig? Wann REST, wann GraphQL, wie Webhooks?

Bei smplx. bauen wir seit 2020 Integrationen zwischen Shopify und externen Systemen. Wir haben gelernt: Die richtige API-Strategie entscheidet, ob deine Integration robust ist oder ständig fehlschlägt.

Die drei Shopify-APIs im Überblick

1. Admin REST API

Was ist das?

REST ist die klassische API. Du sendest HTTP-Requests, bekommst JSON zurück.

Endpoint-Struktur:

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

Pros:

  • Einfach zu verstehen (HTTP, JSON)
  • Gute Dokumentation
  • Viele SDKs (Ruby, Node, PHP, etc.)
  • Vertraut – viele kennen REST

Cons:

  • Over-fetching: Du bekommst Daten, die du nicht brauchst
  • Multiple Requests nötig (z.B. Produkt → Variants → Images)
  • Weniger Abfrage-Kontrolle

Example: Produkt mit Variants abrufen

# Request 1: Produkt
GET /admin/api/2024-01/products/123.json

# Request 2: Variants
GET /admin/api/2024-01/products/123/variants.json

# Request 3: Images
GET /admin/api/2024-01/products/123/images.json

# Total: 3 API-Calls für eine "komplette" Produktinfo

2. Admin GraphQL API

Was ist GraphQL?

GraphQL ist eine Query-Language. Du fragst genau nach dem, was du brauchst – nicht mehr, nicht weniger.

Query-Struktur:

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:

  • Keine Over-fetching
  • Ein Request statt drei
  • Typsystem (kennt alle Felder)
  • Modern, leistungsstark
  • Bessere Error-Messages

Cons:

  • Steiler Learning Curve (was ist GraphQL?)
  • Debugging ist anders als REST
  • Rate Limits sind komplexer

Example: Dasselbe Szenario mit 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' } })
});

// Alles in einem Request!

3. Storefront API

Was ist die Storefront API?

Die Storefront API ist GraphQL, aber für deinen Kunden (nicht Admin). Sie ist öffentlich (mit limitiertem Scope).

Unterschiede zu Admin GraphQL:

  • Kann nicht schreiben (nur lesen)
  • Weniger Informationen (z.B. keine Inventory-Details für andere Variants)
  • Gebaut für Customer-Szenarien (Cart, Checkout)
  • Rate Limits sind anders (großzügiger für Read-Only)

Wann nutzen?

  • Dein Frontend (Headless Shop)
  • Externe Websites (Produktsuchergebnisse einbetten)
  • Mobile Apps

Webhooks: Event-Driven Architecture

Was sind Webhooks?

Statt ständig die API abzufragen ("Gibt es neue Orders?"), sagt dir Shopify: "Achtung, eine neue Order!" – via Webhook.

Architektur:

Kunde kauft → Shopify ruft dein Backend auf (HTTP POST) → Dein Code reagiert

Webhook-Beispiel: Order erstellt

Dein Webhook-Endpoint:

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

// Webhook validieren (wichtig!)
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;

  // Deine Logik hier
  console.log(`Order #${order.order_number} erstellt`);

  // An ERP senden
  sendToERP(order);

  // An Email-Service senden
  sendConfirmationEmail(order.customer);

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

app.listen(3000);

Wichtige Webhook-Sicherheit:

  1. Validiere die Signatur (siehe Code oben)
  2. Mache deine Endpoints idempotent (Webhook könnte 2x ankommen)
  3. Antworte schnell (Shopify wartet nur 30 Sekunden)

Idempotenz-Beispiel

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

  // Prüf: Haben wir diesen Webhook schon verarbeitet?
  const existing = await db.webhooks.findOne({
    shopify_webhook_id: webhookId
  });

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

  // Neu → Verarbeiten
  await processOrder(order);

  // Markieren als verarbeitet
  await db.webhooks.insert({
    shopify_webhook_id: webhookId,
    processed_at: new Date()
  });

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

Wichtig: Du brauchst Idempotenz, weil Webhooks mehrfach ankommen können.

Rate Limits: Wie du sie handhabst

REST API Rate Limits

Quota: 2 API-Calls pro Second (Standard Plan)

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

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

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

GraphQL Rate Limits

Komplexer: Basierend auf Query Complexity, nicht nur Count.

Shopify-Header nach Request:

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

Was das bedeutet:

  • Du hast 1000 "Punkte"
  • Diese Query kostete 10 Punkte
  • Du regenerierst 50 Punkte pro Sekunde
  • Wenn < 100 Punkte: Throttle!
const checkRateLimit = (response) => {
  const cost = JSON.parse(response.headers['graphql-cost']);

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

Real-World: helpyourself Case Study

helpyourself ist ein Medical-Commerce-Shop, der Shopify mit ihrem ERP (iLabServer) synchronisieren muss.

Challenge:

  • 10.000+ Produkte
  • Inventory muss realtime sync sein
  • GDPR-kritisch (Kundendaten)
  • Fehler dürfen nicht happen (medizinische Geräte!)

Lösung: Webhook-Driven mit Retry-Logic

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

  // 2. Sofort: An iLabServer senden (Webhooks müssen schnell sein!)
  const jobId = await queue.add('sync-order-to-erp', {
    orderId: order.id,
    attempt: 1
  }, {
    priority: 'high',
    attempts: 5, // Bis zu 5 Versuche
    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(`Attempt ${attempt} failed:`, error);
    throw error; // Bull wird Retry triggern
  }
});

Warum Webhooks hier brillant sind:

  • Asynchron (Kunden sehen nicht die Verzögerung)
  • Zuverlässig mit Retry-Logic
  • Skalierbar (viele Orders parallel)
  • GDPR-Proof (Daten gehen nicht durchs Frontend)

REST vs. GraphQL: Wann welche?

Szenario REST GraphQL
Einmaliges Daten-Abrufen
Batch-Operationen (1000+ Orders)
Schreiben (Produkte editieren) ❌ (nicht in Admin)
Komplexe Queries (3+ Requests)
Performance-kritisch
Rate Limit Handling wichtig ⚠️ Einfach
Team kennt GraphQL nicht ⚠️

Unsere Regel: GraphQL für komplexe Queries, REST für simple CRUD-Operationen.

Fehler, die wir sehen

Fehler 1: Keine Retry-Logic auf Webhooks

// ❌ FALSCH
app.post('/webhooks/orders/create', async (req, res) => {
  const result = await externalAPI.post(order);
  // Was wenn externalAPI down ist? Order ist verloren!
});

// ✅ RICHTIG
app.post('/webhooks/orders/create', async (req, res) => {
  // Sofort antworten
  res.status(202).send('OK');

  // Dann async verarbeiten mit Retry
  await queue.add('process-order', order, { attempts: 5 });
});

Fehler 2: Keine Webhook-Signatur-Validierung

// ❌ FALSCH: Vertraut einfach jedem POST
app.post('/webhooks/orders/create', (req, res) => {
  processOrder(req.body); // Jeder kann das triggern!
});

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

Fehler 3: Keine Idempotenz-Handling

// ❌ FALSCH
app.post('/webhooks/orders/create', async (req, res) => {
  const order = req.body;
  // Shopify sendet das 2x? Zwei Orders im ERP!
  await erp.createOrder(order);
});

// ✅ RICHTIG
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);
});

Best Practices für robuste Integrationen

1. Event-Driven (Webhooks) statt Polling

// ❌ FALSCH: Alle 10 Sekunden abfragen
setInterval(async () => {
  const orders = await shopify.get('/admin/api/2024-01/orders.json');
  // API-teuer und Datenverzögerung
}, 10000);

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

2. Immer Logging/Monitoring

const logger = require('winston');

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

  try {
    await processOrder(req.body);
    logger.info('Order processed successfully');
  } catch (error) {
    logger.error('Order processing failed', { error: error.message });
    throw error; // Queue wird Retry machen
  }
});

3. Health-Checks bauen

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. Rate Limits proaktiv managen

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

  if (throttleStatus.currentlyAvailable < 200) {
    logger.warn('Rate limit critical', {
      available: throttleStatus.currentlyAvailable
    });
    // Könnte hier Business-Logic triggern (z.B. Batch-Job pausieren)
  }
};

Zusammenfassung: Die API-Strategie für 2026

  1. REST für Simple Operations (ein Produkt ändern, eine Order lesen)
  2. GraphQL für komplexe Queries (viele verwandte Daten mit einem Request)
  3. Webhooks für Event-Driven Architecture (immer wenn es möglich ist)
  4. Storefront API für Kunden-Facing (Headless Shops, Mobile Apps)

Die Kombination aller drei ist oft die beste Lösung.


Über den Autor

Claudio Gerlich ist Technical Architect bei smplx. und seit 2020 spezialisiert auf Shopify-Integrationen. Mit smplx. haben wir komplexe Sync-Szenarien mit ERPs, CRMs und Spezial-Systemen umgesetzt – immer mit einem Fokus auf Zuverlässigkeit und Fehlerbehandlung.

helpyourself ist ein gutes Beispiel für eine robuste Integration: Medical-Grade Reliability mit Webhooks und Retry-Logic.

smplx. ist Shopify Technical Partner (seit 2020) mit Sitz in Coesfeld, NRW.