smplx.

Shopify Architektur

Shopify Integrationen: ERP, CRM & mehr

Claudio Gerlich··Aktualisiert: ·⏱ 13 min
Shopify Integrationen: ERP, CRM & mehr

Im Shopify-Architektur-Ratgeber sprechen wir über Grundlagen. Heute: Die sichtbare Komplexität. Die meisten Shops sind nicht "nur Shopify" – sie sind Shopify + ERP + CRM + Versand + Zahlungen.

Bei smplx. verbinden wir seit 2020 Shopify mit Systemen wie SAP, Xentral, HubSpot und Salesforce. Wir wissen: Die Integration ist die Kunst.

Die drei Integration Patterns

Pattern 1: Webhook-Driven (Event-Based)

Architektur:

Shopify Event → Webhook → Dein System → Externe Systeme

Beispiel: Order erstellt

1. Kunde kauft auf Shopify
2. Shopify sendet Webhook (order/create) an deine App
3. Deine App verarbeitet Webhook async
4. Deine App sagt ERP: "Neue Order!"
5. ERP verarbeitet

Pros:

  • Real-time Reaktion auf Events
  • Asynchron = schnell für Kunden
  • Skalierbar
  • GDPR-freundlich (Daten fließen nicht durchs Frontend)

Cons:

  • Braucht zuverlässiges Backend
  • Webhook-Retry-Handling nötig
  • Debugging ist schwerer

Pattern 2: Polling (Abfragen)

Architektur:

Dein System → Timer (alle X Sekunden) → Shopify API → Verarbeitung

Beispiel:

// Jeden Abend: Alle neuen Orders abholen
setInterval(async () => {
  const orders = await shopify.get('/admin/api/2024-01/orders.json?status=any');

  for (const order of orders) {
    if (!alreadyProcessed(order.id)) {
      await sendToERP(order);
    }
  }
}, 86400000); // 24 Stunden

Pros:

  • Einfach zu verstehen
  • Kein Webhook-Setup nötig
  • Debugging einfacher

Cons:

  • Verzögerung (bis nächster Poll)
  • API-Calls verschwenden
  • Rate Limits problematisch
  • Nicht real-time

Pattern 3: Middleware (Orchestration)

Architektur:

Shopify ↔ Middleware ↔ ERP
  ↓       ↓       ↓
 API    GraphQL  API

Beispiel: helpyourself (Medical Devices)

helpyourself musste Shopify mit iLabServer (medical ERP) verbinden. Sehr sensible Daten.

Shopify Order
    ↓
Webhook → helpyourself Middleware
    ├─ Validiert Daten
    ├─ Maskiert sensitiv Info
    └─ Sendet zu iLabServer
         ├─ Verarbeitet Bestellung
         └─ Sendet Confirmation zurück

Pros:

  • Zentrale Kontrolle
  • Datenschutz-Logik an einem Ort
  • Transformation möglich
  • Audit-Logging leicht

Cons:

  • Komplexere Infrastruktur
  • Single Point of Failure (wenn Middleware down)
  • Mehr zu maintainen

Konkrete Integrationen

1. ERP-Integration (Inventory Sync)

Szenario: Shopify → Xentral (Cloud ERP)

Eine ERP-Anbindung dieser Komplexität bauen wir typischerweise im Architecture+ Service — inkl. Webhook-Design, Retry-Logic und Dead-Letter-Queue.

Challenge:

  • Inventory in Shopify MUSS mit Xentral sync sein
  • Wenn Xentral Bestand ändert → Shopify updaten
  • Rückrichtung: Wenn Shopify Sale → Xentral wissen

Lösung: Bidirektionale Webhooks

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

  // Sofort antworten (wichtig!)
  res.status(202).send('Processing...');

  // Async: Zu Xentral senden
  const job = await queue.add('sync-to-erp', { orderId: order.id });
});

// 2. Background Job
queue.process('sync-to-erp', async (job) => {
  const { orderId } = job.data;
  const order = await shopify.rest.Order.find(orderId);

  // Zu Xentral senden
  const xentralResult = await xentral.api.post('/orders', {
    external_order_id: order.id,
    customer: order.customer,
    items: order.line_items.map(item => ({
      sku: item.sku,
      quantity: item.quantity,
      price: item.price
    })),
    total: order.total_price
  });

  // Logging
  await logSync('order', orderId, 'SUCCESS');
});

// 3. Xentral Webhook: Inventory ändert sich
app.post('/webhooks/xentral/inventory-change', async (req, res) => {
  const { sku, quantity } = req.body;

  res.status(202).send('Syncing...');

  // Shopify Inventory updaten
  const job = await queue.add('update-inventory', { sku, quantity });
});

// 4. Background Job für Inventory
queue.process('update-inventory', async (job) => {
  const { sku, quantity } = job.data;

  // SKU zu Shopify Variant mappen
  const variant = await shopify.graphql(`
    query {
      productVariants(first: 1, query: "sku:${sku}") {
        edges {
          node {
            id
            sku
          }
        }
      }
    }
  `);

  if (variant.edges.length > 0) {
    const variantId = variant.edges[0].node.id;

    // Inventory updaten
    await shopify.graphql(`
      mutation {
        inventorySetQuantities(input: {
          ignoreUnknownLocations: true
          quantities: [{
            inventoryItemId: "${variantId}"
            availableQuantity: ${quantity}
          }]
        }) {
          inventoryItems {
            id
            sku
          }
        }
      }
    `);
  }
});

Wichtig: Rate Limits

// Xentral hat Rate Limits
const xentral = axios.create({
  baseURL: 'https://xentral.api.com',
  headers: { 'Authorization': `Bearer ${XENTRAL_TOKEN}` }
});

// Implementiere Retry-Logic
const retryConfig = {
  retries: 3,
  retryDelay: exponentialBackoff
};

xentral.interceptors.response.use(null, async (error) => {
  if (error.response.status === 429) {
    // Rate Limited! Warte exponentiell länger
    const delay = Math.pow(2, attempt) * 1000;
    await new Promise(resolve => setTimeout(resolve, delay));
    return retryRequest(error.config);
  }
});

2. CRM-Integration (HubSpot)

Szenario: Shopify Customer → HubSpot Contact

Challenge:

  • Neue Kunden in Shopify → HubSpot
  • Order-Daten → HubSpot Deal
  • Weitere Interaktionen synced

Lösung: Event-Driven mit HubSpot API

// 1. Customer erstellt → HubSpot Contact
app.post('/webhooks/customers/create', async (req, res) => {
  const customer = req.body;

  res.status(202).send('Processing...');

  const job = await queue.add('sync-customer-to-hubspot', {
    customerId: customer.id,
    email: customer.email
  });
});

// 2. Background Job
queue.process('sync-customer-to-hubspot', async (job) => {
  const { customerId, email } = job.data;

  try {
    // HubSpot Contact erstellen
    const contact = await hubspot.post('/crm/v3/objects/contacts', {
      associations: [],
      properties: {
        firstname: customer.first_name,
        lastname: customer.last_name,
        email: customer.email,
        phone: customer.phone,
        lifecyclestage: 'customer',
        // Custom Properties
        shopify_customer_id: customerId,
        shopify_total_spent: customer.total_spent,
        shopify_order_count: customer.orders_count
      }
    });

    // Speichere HubSpot ID zurück in Shopify Metafield
    await shopify.graphql(`
      mutation {
        metafieldsSet(ownerId: "gid://shopify/Customer/${customerId}", metafields: {
          key: "hubspot_contact_id"
          value: "${contact.id}"
          type: "single_line_text"
        }) {
          metafields { id }
        }
      }
    `);

  } catch (error) {
    logger.error('HubSpot sync failed', { email, error: error.message });
    throw error; // Queue wird Retry machen
  }
});

// 3. Order → HubSpot Deal
app.post('/webhooks/orders/create', async (req, res) => {
  const order = req.body;

  res.status(202).send('Processing...');

  const job = await queue.add('sync-order-to-hubspot', {
    orderId: order.id,
    customerId: order.customer.id,
    amount: order.total_price
  });
});

// 4. Order zu Deal in HubSpot
queue.process('sync-order-to-hubspot', async (job) => {
  const { orderId, customerId, amount } = job.data;

  // Hole HubSpot Contact ID aus Metafield
  const customer = await shopify.graphql(`
    query {
      customer(id: "gid://shopify/Customer/${customerId}") {
        hubspotContactId: metafield(namespace: "custom", key: "hubspot_contact_id") {
          value
        }
      }
    }
  `);

  const hubspotContactId = customer.hubspotContactId?.value;

  if (!hubspotContactId) {
    throw new Error('HubSpot Contact ID not found');
  }

  // Deal erstellen
  const deal = await hubspot.post('/crm/v3/objects/deals', {
    associations: [{
      types: [{ associationType: 'contact_to_deal' }],
      id: hubspotContactId
    }],
    properties: {
      dealname: `Order #${orderId}`,
      dealstage: 'closedwon',
      amount: amount,
      closedate: Date.now(),
      shopify_order_id: orderId
    }
  });
});

3. Payment Provider Integration

Szenario: Stripe/Klarna mit Shopify

Challenge:

  • Zahlung autorisiert in Shopify
  • Müssen in Payment Provider markiert sein
  • Disputes/Refunds sollten bidirektional sync

Shopify macht es einfach:

  • Built-in Stripe, Klarna, Adyen
  • Webhooks automatic
  • Kein Custom-Code nötig (meist)

Aber wenn Custom Integration:

// Stripe → Shopify (Payment erfolgreich)
app.post('/webhooks/stripe/charge.succeeded', async (req, res) => {
  const event = req.body;
  const charge = event.data.object;

  // Finde die Shopify Order
  const shopifyOrderId = charge.metadata.shopify_order_id;

  // Mark as Paid in Shopify
  await shopify.graphql(`
    mutation {
      orderMarkAsPaid(input: { id: "gid://shopify/Order/${shopifyOrderId}" }) {
        order {
          id
          displayFinancialStatus
        }
      }
    }
  `);
});

// Shopify → Stripe (Refund)
app.post('/webhooks/shopify/refunds/create', async (req, res) => {
  const { refund, orders } = req.body;
  const order = orders[0];

  const stripeChargeId = order.metafields?.payment?.stripe_charge_id?.value;

  if (stripeChargeId) {
    await stripe.refunds.create({
      charge: stripeChargeId,
      amount: Math.round(refund.transactions[0].amount * 100) // In Cent
    });
  }
});

Real-World: helpyourself Case Study

helpyourself ist ein Medical-Devices Shop mit GDPR-kritischen Anforderungen.

Challenge:

  • Kundendaten müssen GDPR-konform fließen
  • Bestellungen müssen zu iLabServer (proprietärer ERP)
  • Keine Datenschleifen zwischen Systemen
  • Audit-Logging für medizinische Compliance

Lösung: Zentrale Middleware

// helpyourself Middleware Architecture

class HelpyourselfMiddleware {
  constructor() {
    this.shopify = new ShopifyAPI({ ... });
    this.ilab = new iLabServerAPI({ ... });
  }

  // Webhook von Shopify kommt rein
  async handleOrderWebhook(order) {
    // 1. Validiere Order
    if (!this.validateOrder(order)) {
      throw new Error('Invalid order structure');
    }

    // 2. Daten-Mapping mit Datenschutz
    const transformedOrder = this.transformOrderForILab(order);

    // 3. Sensitiv-Daten schützen
    const maskedOrder = this.maskSensitiveData(transformedOrder);

    // 4. Sende zu iLab mit Retry
    try {
      const ilabResult = await this.ilab.createOrder(maskedOrder);

      // 5. Speichere iLab ID zurück in Shopify
      await this.shopify.updateOrderMetafield(order.id, {
        ilab_order_id: ilabResult.id,
        sync_status: 'success',
        sync_timestamp: new Date().toISOString()
      });

      // 6. Audit Log
      this.auditLog('order_synced', {
        shopify_order_id: order.id,
        ilab_order_id: ilabResult.id,
        timestamp: new Date(),
        gdpr_compliant: true
      });

    } catch (error) {
      // 7. Fehlerbehandlung
      logger.error('Order sync failed', {
        orderId: order.id,
        error: error.message
      });

      // Queue für Retry
      await this.queue.add('retry_order_sync', { orderId: order.id });
    }
  }

  // Datenschutz-Transformationen
  maskSensitiveData(order) {
    return {
      ...order,
      customer: {
        // Nur notwendige Daten
        email: order.customer.email,
        phone: order.customer.phone,
        // Kredit-Kartendaten NICHT mitschicken
        // Shopify speichert die nicht sowieso
      },
      billing_address: {
        // Ganze Adresse
      },
      shipping_address: {
        // Ganze Adresse
      }
      // Aber keine IPs, Cookies, Browser-Fingerprints!
    };
  }

  // GDPR-Datenabfrage
  async deleteCustomerData(customerId) {
    // 1. Shopify löschen
    await this.shopify.deleteCustomer(customerId);

    // 2. iLab löschen (wenn möglich)
    try {
      await this.ilab.deleteCustomer(customerId);
    } catch (error) {
      // Wenn iLab nicht löschen kann: Log für DSGVO
      this.auditLog('customer_delete_failed', {
        customerId,
        system: 'ilab',
        reason: error.message
      });
    }

    // 3. Middleware-Logs löschen (außer für gesetzlich vorgeschriebene Aufbewahrung)
    await this.database.deleteCustomerLogs(customerId);
  }
}

Wichtig: Medical Data ist streng reguliert. helpyourself brauchte:

  • Audit Logs für jede Operation
  • Encryption in Transit (TLS)
  • Encryption at Rest
  • Data Residency (DSGVO: EU-Daten bleiben in EU)
  • Deletion Workflows

GDPR in Integrationen

Deine Verantwortung:

  • Daten nur weitergeben mit expliziter Zustimmung
  • Kunde kann Daten löschen ("Right to be Forgotten")
  • Daten nicht länger speichern als nötig
  • Transparent sein über Datenfluss

Praktisch:

// ❌ FALSCH: Daten automatisch teilen
app.post('/webhooks/orders/create', async (req, res) => {
  const order = req.body;
  // Sende Alles zu Facebook Pixel, Google Analytics, etc.
  await sendToFacebook(order);
  await sendToGoogle(order);
});

// ✅ RICHTIG: Nur mit Zustimmung
app.post('/webhooks/orders/create', async (req, res) => {
  const order = req.body;

  if (order.customer.accepts_marketing) {
    // Nur dann Daten weiterteilen
    await sendToFacebook(order);
  }

  if (customer.accepts_email_marketing) {
    // Nur dann zu Email-Service
    await sendToMailchimp(order.customer);
  }
});

// Lösch-Workflow
app.post('/api/delete-my-data', async (req, res) => {
  const { customerId } = req.body;

  // 1. Shopify
  await shopify.deleteCustomer(customerId);

  // 2. Alle Third-Party Services
  await facebook.deleteCustomer(customerId);
  await google.deleteCustomer(customerId);
  await mailchimp.deleteCustomer(customerId);

  // 3. Deine Logs
  await database.deleteCustomerData(customerId);
});

Integration Checklist

  • Wähle Pattern: Webhook, Polling, oder Middleware?
  • Rate Limits handhabst? (Retry-Logic)
  • Webhook-Idempotenz implementiert? (kein Duplicate-Processing)
  • Error-Logging auf beide Seiten? (Debugging später)
  • GDPR-Datenfluss überprüft?
  • Datenformats validiert? (JSON Schema)
  • Sensitive Data maskiert?
  • Monitoring aktiv? (Health Checks)
  • Fehlerfall-Handling? (Was wenn External API down?)
  • Audit-Logging?

Über den Autor

Claudio Gerlich ist Technical Architect bei smplx. und seit 2020 spezialisiert auf Shopify-Integrationen. Mit helpyourself haben wir bewiesen, dass auch medizinisch-regulierte Daten sicher über Webhook-Integrationen fließen können – wenn die Architektur right ist.

Integrationen sind nicht einfach. Aber mit den richtige Patterns werden sie machbar.

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

Weiterlesen

Shopify Architektur

Shopify API: REST, GraphQL & Webhooks

Shopify bietet REST, GraphQL und Webhooks. Wir zeigen dir wann du welche API nutzt, wie du Rate Limits handhabst und wie du robuste Integrationen baust.

⏱ 13 min

Shopify API: REST, GraphQL & Webhooks
Shopify Architektur

Shopify Metaobjects erklärt: Wann, warum und wie

Metaobjects sind die Evolution von Metafields. So baust du komplexe Datenstrukturen in Shopify – mit Code-Beispielen.

⏱ 12 min

Shopify Metaobjects erklärt: Wann, warum und wie
Shopify Architektur

Testing und QA für Shopify-Projekte: Checkliste

QA für e-Commerce ist kritisch. Wir zeigen dir Testing-Strategien, Tools, Pre-Launch-Checklisten und wie du nach Launch monitoren solltest.

⏱ 12 min

Testing und QA für Shopify-Projekte: Checkliste

Bereit, das in deinem Shopify-Store zu fixen?

In 5 Werktagen weißt du genau, was du zuerst fixen musst — 990 €, kein Pitch.

WhatsApp Chat