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.