smplx.
Shopify Architektur

Third-Party-Integrationen in Shopify: ERP, CRM und mehr [2026]

Claudio Gerlich··13 min

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)

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.