
Shopify mit ERP verbinden: Der komplette Integrations-Guide (2026)
Morgens um 7:14 Uhr: Ihr SAP zeigt 38 Stück eines Produkts, Shopify zeigt 51. Um 7:42 bestellt ein Kunde 45 Stück. Shopify nimmt die Bestellung an — Ihr Lager kann sie nicht erfüllen. Was folgt: eine peinliche Storno-Mail, ein verlorener Kunde und ein Ticket im Support-System, das niemand braucht.
Das ist kein Edge Case. Das ist der Alltag von Shopify-Händlern, deren ERP und Shop nicht sauber miteinander sprechen. Und je mehr Kanäle Sie bespielen — Shopify, Amazon, stationärer Handel — desto explosiver wird das Problem.
In diesem Guide zeige ich Ihnen, wie Sie Shopify zuverlässig mit Ihrem ERP verbinden. Welcher Ansatz zu Ihrem Setup passt, welche Daten synchronisiert werden müssen, wie die technische Architektur aussieht und was das Ganze kostet. Mit echten Code-Beispielen, konkreten ERP-Tipps für SAP, Dynamics, JTL, Xentral, WeClapp und Billbee — und den Red Flags, bei denen Sie wissen: hier brauchen Sie eine Custom-Lösung.
Warum ERP-Integration essentiell ist
Ohne eine saubere Integration zwischen Shopify und Ihrem ERP-System laufen Sie in drei Kategorien von Problemen:
1. Doppelte Dateneingabe
Jede Bestellung, die in Shopify eingeht, muss manuell ins ERP übertragen werden. Bei 20 Bestellungen am Tag dauert das vielleicht 45 Minuten. Bei 200 Bestellungen sind es 4–5 Stunden — jeden Tag. Und jede manuelle Eingabe ist eine potenzielle Fehlerquelle: falsche Artikelnummern, vertauschte Adressen, fehlende Positionen.
2. Bestandsdiskrepanzen
Ihr ERP ist die Single Source of Truth für Bestände. Ohne Echtzeit-Sync zeigt Shopify veraltete Lagerbestände an. Die Folgen:
- Überverkäufe: Kunden bestellen Produkte, die nicht mehr da sind
- Unterverkäufe: Eigentlich verfügbare Produkte werden als "ausverkauft" angezeigt, weil der letzte Sync 6 Stunden her ist
- Kanalkonflikt: Ein Artikel wird gleichzeitig auf Shopify und Amazon verkauft — der Bestand reicht nur für eine Bestellung
3. Versand-Chaos
Wenn das Fulfillment-Team im ERP arbeitet und Tracking-Nummern dort einpflegt, Shopify aber nichts davon mitbekommt:
- Kunden erhalten keine Versandbenachrichtigung
- Der Shopify-Status bleibt auf "Unfulfilled" stehen
- Kunden öffnen Disputes oder schicken Support-Anfragen — obwohl ihr Paket längst unterwegs ist
Die harten Zahlen
| Problem | Kosten (geschätzt, bei 500 Bestellungen/Monat) |
|---|---|
| Manuelle Dateneingabe | 1.500–2.500 €/Monat (Personalkosten) |
| Überverkäufe & Stornos | 800–3.000 €/Monat (Umsatzverlust + Kundenverlust) |
| Support-Aufwand | 500–1.200 €/Monat (unnötige Anfragen) |
| Gesamt | 2.800–6.700 €/Monat |
Fazit: Eine ERP-Integration, die 200–500 € im Monat kostet, amortisiert sich bereits bei 100+ Bestellungen pro Monat. Bei 500+ Bestellungen ist es ein No-Brainer.
Die 3 Integrations-Ansätze im Vergleich
Es gibt grundsätzlich drei Wege, Shopify mit einem ERP zu verbinden. Jeder hat seinen Sweet Spot — und seine Grenzen.
Überblick
| Kriterium | Standard-Connector | iPaaS / Middleware | Custom Integration |
|---|---|---|---|
| Beispiele | Synesty, Tradebyte, Channable | Celigo, Make (Integromat), Workato | Eigene Node.js/Python-Middleware |
| Setup-Zeit | 1–5 Tage | 1–4 Wochen | 4–12 Wochen |
| Monatliche Kosten | 50–200 €/Monat | 200–800 €/Monat | 200–500 € (Hosting + Wartung) |
| Einmalkosten | 0–500 € | 500–3.000 € (Setup) | 5.000–15.000 € (Entwicklung) |
| Flexibilität | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Skalierbarkeit | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Wartungsaufwand | Gering | Mittel | Mittel–Hoch |
| Geeignet für | Standard-ERPs, einfache Flows | Multi-System-Setups, moderate Komplexität | Hochkomplexe Logik, hohe Volumina |
Ansatz 1: Standard-Connector
Standard-Connectoren wie Synesty Studio, Tradebyte oder Channable bieten vorgefertigte Verbindungen zwischen Shopify und gängigen ERPs. Sie konfigurieren Mappings über eine Web-Oberfläche — kein Code nötig.
Vorteile:
- Schnellster Weg zur Integration
- Kein Entwicklerwissen nötig
- Bewährte Mappings für Standard-ERPs
- Regelmäßige Updates durch den Anbieter
Nachteile:
- Begrenzte Anpassbarkeit — komplexe Geschäftslogik ist nicht abbildbar
- Rate Limits bei vielen SKUs oder hohem Bestellvolumen
- Vendor Lock-in: Wenn der Anbieter Features ändert oder eingestellt wird, haben Sie ein Problem
- Oft nur periodische Syncs (alle 15–60 Minuten), kein Echtzeit
Am besten für: Shops mit < 500 SKUs, einem ERP-System und Standard-Prozessen.
Ansatz 2: iPaaS / Middleware
Integration-Platforms wie Celigo, Make (ehemals Integromat) oder Workato bieten visuelle Flow-Builder mit deutlich mehr Flexibilität als Standard-Connectoren.
Vorteile:
- Visueller Flow-Builder — leichter zu verstehen und zu debuggen
- Hunderte vorgefertigte Konnektoren
- Bedingte Logik, Schleifen, Error Handling eingebaut
- Gute Monitoring-Dashboards
Nachteile:
- Kosten skalieren mit Volumen (Operationen/Monat)
- Komplexe Transformationen stoßen an die Grenzen visueller Builder
- Performance bei Bulk-Operationen (z.B. 10.000 Preisänderungen) oft unzureichend
- Latenzen bei verketteten Flows
Am besten für: Multi-System-Landschaften (ERP + PIM + WMS), moderate Komplexität, < 2.000 Bestellungen/Tag.
Ansatz 3: Custom Integration
Eine eigene Middleware, typischerweise als Node.js- oder Python-Service, der zwischen Shopify und dem ERP vermittelt.
Vorteile:
- Volle Kontrolle über Logik, Timing und Fehlerbehandlung
- Optimale Performance — Sie bestimmen die Architektur
- Keine Vendor-Abhängigkeit
- Beliebig erweiterbar: Heute Shopify + SAP, morgen + Amazon + WMS
Nachteile:
- Höhere Initialkosten
- Erfordert Entwickler-Know-how (intern oder extern)
- Wartung und Monitoring liegen bei Ihnen
- Longer Time-to-Market
Am besten für: Shops mit > 1.000 Bestellungen/Tag, komplexer Geschäftslogik, Multi-Channel oder hochspezifischen ERP-Anforderungen.
Meine Empfehlung: Starten Sie nicht automatisch mit Custom. Wenn ein Standard-Connector Ihre Anforderungen zu 80 %+ abdeckt, nutzen Sie ihn. Erst wenn Sie regelmäßig an dessen Grenzen stoßen, lohnt sich eine Custom-Lösung. Mehr dazu im Abschnitt Red Flags.
Welche Daten werden synchronisiert?
Eine vollständige ERP-Integration umfasst typischerweise fünf Datendomänen. Die Richtung der Synchronisation ist entscheidend:
| Datendomäne | Richtung | Häufigkeit | Priorität |
|---|---|---|---|
| Produkte & Varianten | ERP → Shopify | Bei Änderung / täglich | Hoch |
| Preise | ERP → Shopify | Bei Änderung / täglich | Hoch |
| Lagerbestände | ERP → Shopify | Near-Realtime (< 5 Min) | Kritisch |
| Bestellungen | Shopify → ERP | Echtzeit (Webhook) | Kritisch |
| Kunden | Shopify → ERP (primär) | Bei Bestellung | Mittel |
| Fulfillment / Tracking | ERP → Shopify | Bei Versand | Hoch |
| Retouren / Gutschriften | Bidirektional | Bei Ereignis | Hoch |
Produkte & Varianten: ERP → Shopify
Das ERP ist in den meisten Setups der Product Master. Neue Artikel, Variantenänderungen, Beschreibungen und Kategorien werden im ERP gepflegt und nach Shopify synchronisiert.
# Shopify GraphQL: Produkt-Update via Admin API
mutation productUpdate($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
title
variants(first: 10) {
edges {
node {
id
sku
price
inventoryQuantity
}
}
}
}
userErrors {
field
message
}
}
}
Wichtig: Verwenden Sie bei der Synchronisation immer die SKU als gemeinsamen Identifier. Die Shopify-ID und die ERP-Artikelnummer ändern sich unabhängig voneinander — die SKU bleibt als Brücke konstant.
Lagerbestände: Die kritischste Sync
Bestandsdaten müssen so nahe an Echtzeit wie möglich synchronisiert werden. Ein Delta von 30 Minuten kann bei schnelldrehenden Artikeln bereits zu Überverkäufen führen.
// Bestandsabgleich via Shopify REST API
async function syncInventoryToShopify(sku, erpQuantity, locationId) {
// 1. Variante über SKU finden
const variant = await shopify.variant.list({
fields: 'id,sku,inventory_item_id',
limit: 1
}).then(variants => variants.find(v => v.sku === sku));
if (!variant) {
console.error(`SKU ${sku} nicht in Shopify gefunden`);
return;
}
// 2. Bestand setzen (nicht relativ, sondern absolut!)
await shopify.inventoryLevel.set({
inventory_item_id: variant.inventory_item_id,
location_id: locationId,
available: erpQuantity
});
console.log(`Bestand für SKU ${sku}: ${erpQuantity} Stück gesetzt`);
}
Tipp: Verwenden Sie immer absolute Bestandswerte (
set), nicht relative Anpassungen (adjust). Bei relativen Werten akkumulieren sich Synchronisationsfehler über die Zeit — ein klassischer Bug, den ich regelmäßig in bestehenden Integrationen finde.
Bestellungen: Shopify → ERP
Bestellungen müssen sofort nach Eingang ins ERP übertragen werden, damit Fulfillment, Buchhaltung und Bestandsreservierung funktionieren. Hier ist Webhook-basierte Echtzeit-Übertragung Pflicht.
// Bestellung für das ERP transformieren
function transformOrderForERP(shopifyOrder) {
return {
externalOrderId: shopifyOrder.name, // z.B. "#1042"
shopifyId: shopifyOrder.id,
orderDate: shopifyOrder.created_at,
customer: {
email: shopifyOrder.email,
firstName: shopifyOrder.billing_address?.first_name,
lastName: shopifyOrder.billing_address?.last_name,
company: shopifyOrder.billing_address?.company || null,
vatId: shopifyOrder.note_attributes?.find(
a => a.name === 'vat_id'
)?.value || null
},
shippingAddress: mapAddress(shopifyOrder.shipping_address),
billingAddress: mapAddress(shopifyOrder.billing_address),
lineItems: shopifyOrder.line_items.map(item => ({
sku: item.sku,
quantity: item.quantity,
unitPrice: parseFloat(item.price),
discount: parseFloat(item.total_discount) / item.quantity,
taxRate: item.tax_lines?.[0]?.rate || 0.19
})),
shipping: {
method: shopifyOrder.shipping_lines?.[0]?.title,
cost: parseFloat(shopifyOrder.shipping_lines?.[0]?.price || 0),
taxRate: 0.19
},
payment: {
method: shopifyOrder.payment_gateway_names?.[0],
status: shopifyOrder.financial_status,
transactionId: shopifyOrder.checkout_token
},
currency: shopifyOrder.currency,
totalPrice: parseFloat(shopifyOrder.total_price),
note: shopifyOrder.note || ''
};
}
Fulfillment & Tracking: ERP → Shopify
Wenn im ERP oder WMS der Versand gebucht wird, muss Shopify die Tracking-Nummer erhalten, damit der Kunde seine Versandbestätigung bekommt:
// Fulfillment nach Shopify zurückmelden
async function pushFulfillmentToShopify(orderId, trackingInfo) {
// Zuerst den Fulfillment Order abrufen
const response = await shopifyGraphQL(`
mutation fulfillmentCreateV2($fulfillment: FulfillmentV2Input!) {
fulfillmentCreateV2(fulfillment: $fulfillment) {
fulfillment {
id
status
trackingInfo {
number
url
company
}
}
userErrors {
field
message
}
}
}
`, {
fulfillment: {
lineItemsByFulfillmentOrder: [{
fulfillmentOrderId: orderId
}],
trackingInfo: {
number: trackingInfo.trackingNumber,
url: trackingInfo.trackingUrl,
company: trackingInfo.carrier // z.B. "DHL", "DPD", "GLS"
},
notifyCustomer: true
}
});
return response.data.fulfillmentCreateV2.fulfillment;
}
Technische Architektur: Webhooks vs. Polling
Die zwei grundlegenden Architektur-Muster für die Synchronisation:
Webhook-basiert (Event-Driven)
Shopify sendet bei jedem relevanten Ereignis (neue Bestellung, Produktänderung, etc.) einen HTTP-POST an Ihren Endpunkt. Das ist der empfohlene Ansatz — er ist echtzeitnah, effizient und verbraucht keine API-Calls.
┌───────────┐ Webhook POST ┌──────────────┐ API Call ┌─────────┐
│ Shopify │ ──────────────────▶│ Middleware │ ─────────────▶│ ERP │
│ │ │ (Queue + │ │ │
│ │ REST/GraphQL │ Worker) │ Response │ │
│ │ ◀──────────────────│ │ ◀─────────────│ │
└───────────┘ └──────────────┘ └─────────┘
Vorteile:
- Near-Realtime (< 5 Sekunden)
- Kein API-Rate-Limit-Verbrauch für die Überwachung
- Skaliert besser — nur Verarbeitung bei tatsächlichen Ereignissen
Nachteile:
- Webhooks können fehlschlagen oder verzögert ankommen
- Idempotenz muss implementiert werden (Shopify sendet ggf. doppelt)
- Initiale Synchronisation braucht trotzdem Polling/Bulk
Polling-basiert
Ihr System fragt Shopify in regelmäßigen Intervallen nach Änderungen ab.
// Polling: Neue Bestellungen alle 5 Minuten abrufen
async function pollNewOrders() {
const lastSync = await getLastSyncTimestamp('orders');
const orders = await shopify.order.list({
status: 'any',
created_at_min: lastSync,
limit: 250,
fields: 'id,name,created_at,line_items,shipping_address,financial_status'
});
for (const order of orders) {
await processOrderForERP(order);
}
await setLastSyncTimestamp('orders', new Date().toISOString());
}
// Cron: Alle 5 Minuten
setInterval(pollNewOrders, 5 * 60 * 1000);
Vorteile:
- Einfacher zu implementieren
- Kein öffentlicher Endpunkt nötig (gut hinter Firewalls)
- Zuverlässiger — Sie kontrollieren den Abrufzeitpunkt
Nachteile:
- Verbraucht API-Calls (Rate Limits!)
- Latenz je nach Intervall (5 Min = 5 Min Verzögerung)
- Skaliert schlecht bei vielen Datenquellen
Meine Empfehlung: Hybrid-Architektur
In der Praxis kombiniere ich beide Ansätze:
- Webhooks für zeitkritische Events: neue Bestellungen, Bestandsänderungen, Fulfillment-Updates
- Polling als Safety Net: ein täglicher Reconciliation-Job, der prüft, ob alle Bestellungen und Bestände korrekt synchronisiert sind
- Queue-basierte Verarbeitung: Webhooks schreiben in eine Message Queue (z.B. Redis, RabbitMQ, SQS), Worker verarbeiten asynchron
// Hybrid-Architektur: Webhook-Empfang + Queue
import { Queue } from 'bullmq';
import { createHmac } from 'crypto';
const orderQueue = new Queue('shopify-orders', {
connection: { host: 'localhost', port: 6379 }
});
// Webhook-Endpunkt
app.post('/webhooks/orders/create', async (req, res) => {
// 1. Webhook verifizieren (WICHTIG!)
const hmac = req.headers['x-shopify-hmac-sha256'];
const verified = verifyWebhook(req.rawBody, hmac);
if (!verified) {
return res.status(401).send('Unauthorized');
}
// 2. Sofort in Queue schreiben — nicht inline verarbeiten!
await orderQueue.add('process-order', {
orderId: req.body.id,
orderName: req.body.name,
payload: req.body,
receivedAt: new Date().toISOString()
}, {
attempts: 5,
backoff: { type: 'exponential', delay: 5000 },
removeOnComplete: 1000,
removeOnFail: 5000
});
// 3. Sofort 200 zurückgeben — Shopify wartet nur 5 Sekunden
res.status(200).send('OK');
});
Dieses Pattern — sofort bestätigen, asynchron verarbeiten — ist das A und O einer stabilen Webhook-Architektur. Wenn Ihre Verarbeitung länger als 5 Sekunden dauert, markiert Shopify den Webhook als fehlgeschlagen und versucht erneut. Ohne Queue landen Sie in einer Endlosschleife aus Retries.
Wenn Sie tiefer in Queue-basierte Architekturen einsteigen möchten, empfehle ich meinen Artikel über Shopify Automatisierung: Custom-Prozesse, der diese Patterns im Detail behandelt.
Shopify Webhook Best Practices
Webhooks sind das Rückgrat jeder Shopify-Integration. Hier sind die wichtigsten Best Practices, die ich nach dutzenden Integrationsprojekten zusammengetragen habe:
1. Webhook-Registrierung via API
Registrieren Sie Webhooks programmatisch, nicht manuell im Admin-Dashboard. So können Sie bei einem Re-Deploy alle Webhooks automatisch neu registrieren:
// Webhooks programmatisch registrieren
const REQUIRED_WEBHOOKS = [
{ topic: 'orders/create', address: `${BASE_URL}/webhooks/orders/create` },
{ topic: 'orders/updated', address: `${BASE_URL}/webhooks/orders/updated` },
{ topic: 'orders/cancelled', address: `${BASE_URL}/webhooks/orders/cancelled` },
{ topic: 'products/update', address: `${BASE_URL}/webhooks/products/update` },
{ topic: 'inventory_levels/update', address: `${BASE_URL}/webhooks/inventory/update` },
{ topic: 'refunds/create', address: `${BASE_URL}/webhooks/refunds/create` },
{ topic: 'fulfillments/create', address: `${BASE_URL}/webhooks/fulfillments/create` },
{ topic: 'app/uninstalled', address: `${BASE_URL}/webhooks/app/uninstalled` }
];
async function registerAllWebhooks() {
// Bestehende Webhooks abrufen
const existing = await shopify.webhook.list();
for (const webhook of REQUIRED_WEBHOOKS) {
const exists = existing.find(
e => e.topic === webhook.topic && e.address === webhook.address
);
if (!exists) {
await shopify.webhook.create({
topic: webhook.topic,
address: webhook.address,
format: 'json'
});
console.log(`Webhook registriert: ${webhook.topic}`);
}
}
}
2. HMAC-Verifizierung — IMMER
Jeder eingehende Webhook muss auf Authentizität geprüft werden. Shopify signiert jeden Webhook mit Ihrem Shared Secret:
import { createHmac, timingSafeEqual } from 'crypto';
function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
const generatedHmac = createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('base64');
// Timing-safe Vergleich gegen Timing-Angriffe
try {
return timingSafeEqual(
Buffer.from(generatedHmac),
Buffer.from(hmacHeader)
);
} catch {
return false;
}
}
// Express Middleware
function webhookAuth(req, res, next) {
const hmac = req.headers['x-shopify-hmac-sha256'];
if (!hmac || !verifyShopifyWebhook(req.rawBody, hmac, SHOPIFY_WEBHOOK_SECRET)) {
console.warn('Webhook-Verifizierung fehlgeschlagen', {
ip: req.ip,
topic: req.headers['x-shopify-topic']
});
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
Achtung: Sie brauchen den Raw Body (Buffer), nicht den geparsten JSON-Body. Wenn Sie Express mit
express.json()verwenden, müssen Sie den Raw Body separat speichern — sonst schlägt die HMAC-Verifizierung fehl.
3. Idempotenz implementieren
Shopify kann denselben Webhook mehrfach senden — z.B. bei Netzwerkproblemen oder Timeouts. Ihre Verarbeitung muss damit umgehen können:
// Idempotenz via Webhook-ID
const processedWebhooks = new Map(); // In Produktion: Redis oder DB
async function handleWebhookIdempotent(webhookId, topic, handler) {
const key = `${topic}:${webhookId}`;
// Bereits verarbeitet?
if (processedWebhooks.has(key)) {
console.log(`Webhook ${key} bereits verarbeitet — übersprungen`);
return { status: 'duplicate', skipped: true };
}
// In Produktion: Distributed Lock (z.B. Redis SETNX)
processedWebhooks.set(key, Date.now());
try {
const result = await handler();
return { status: 'processed', result };
} catch (error) {
// Bei Fehler: Idempotenz-Marker entfernen, damit Retry funktioniert
processedWebhooks.delete(key);
throw error;
}
}
// Verwendung im Webhook-Handler
app.post('/webhooks/orders/create', webhookAuth, async (req, res) => {
const webhookId = req.headers['x-shopify-webhook-id'];
const result = await handleWebhookIdempotent(
webhookId,
'orders/create',
() => processNewOrder(req.body)
);
res.status(200).json(result);
});
4. Retry-Logik verstehen
Shopify wiederholt fehlgeschlagene Webhooks wie folgt:
| Versuch | Wartezeit | Kumuliert |
|---|---|---|
| 1 | Sofort | 0 |
| 2 | 5 Sekunden | 5s |
| 3 | 5 Minuten | ~5 Min |
| 4 | 30 Minuten | ~35 Min |
| 5 | 2 Stunden | ~2,5 Std |
| 6 | 4 Stunden | ~6,5 Std |
| ... | Bis zu 48 Stunden | Max 19 Versuche |
Nach 48 Stunden ohne erfolgreiche Zustellung wird der Webhook automatisch deaktiviert. Sie erhalten keine Benachrichtigung — ein stiller Fehler. Deshalb ist Monitoring (dazu später mehr) unverzichtbar.
ERP-spezifische Tipps
Jedes ERP-System hat seine Eigenarten. Hier die wichtigsten Patterns für die gängigsten Systeme im DACH-Raum:
SAP Business One
SAP Business One bietet seit Version 10 eine Service Layer REST API. Ältere Versionen nutzen die DI API (COM-basiert) oder den Integration Framework.
// SAP Business One Service Layer: Login + Bestellung anlegen
class SAPBusinessOneClient {
constructor(baseUrl, companyDB) {
this.baseUrl = baseUrl;
this.companyDB = companyDB;
this.sessionId = null;
}
async login(username, password) {
const response = await fetch(`${this.baseUrl}/Login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
CompanyDB: this.companyDB,
UserName: username,
Password: password
})
});
const data = await response.json();
this.sessionId = data.SessionId;
return this.sessionId;
}
async createSalesOrder(orderData) {
const response = await fetch(`${this.baseUrl}/Orders`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `B1SESSION=${this.sessionId}`
},
body: JSON.stringify({
CardCode: orderData.customerCode,
DocDate: orderData.orderDate,
DocumentLines: orderData.lineItems.map((item, index) => ({
LineNum: index,
ItemCode: item.sku,
Quantity: item.quantity,
UnitPrice: item.unitPrice,
TaxCode: 'S1' // Standard DE MwSt
})),
AddressExtension: {
ShipToStreet: orderData.shippingAddress.street,
ShipToCity: orderData.shippingAddress.city,
ShipToZipCode: orderData.shippingAddress.zip,
ShipToCountry: orderData.shippingAddress.countryCode
},
U_ShopifyOrderId: orderData.shopifyOrderId // Custom UDF
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`SAP Error: ${error.error.message.value}`);
}
return response.json();
}
}
SAP-spezifische Fallstricke:
- Die Session läuft nach 30 Minuten ab — implementieren Sie automatisches Re-Login
- Custom UDFs (User-Defined Fields) müssen vorab in SAP angelegt werden
- Batch-Requests sind über
$batchmöglich, aber begrenzt auf 100 Operationen - Achten Sie auf das Feld
U_ShopifyOrderId— es verhindert Doppelbuchungen
Microsoft Dynamics NAV / Business Central
Dynamics Business Central bietet eine OData v4 REST API und seit 2024 deutlich verbesserte API-Endpunkte:
// Dynamics Business Central: Bestellung anlegen via OData API
class DynamicsBCClient {
constructor(baseUrl, tenantId, clientId, clientSecret) {
this.baseUrl = baseUrl;
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
async getAccessToken() {
const tokenUrl = `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/token`;
const params = new URLSearchParams({
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
scope: `${this.baseUrl}/.default`
});
const response = await fetch(tokenUrl, {
method: 'POST',
body: params
});
const data = await response.json();
return data.access_token;
}
async createSalesOrder(shopifyOrder) {
const token = await this.getAccessToken();
// 1. Auftragskopf anlegen
const orderResponse = await fetch(
`${this.baseUrl}/api/v2.0/salesOrders`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
customerNumber: shopifyOrder.customerCode,
orderDate: shopifyOrder.orderDate,
externalDocumentNumber: shopifyOrder.shopifyOrderName,
shippingPostalAddress: {
street: shopifyOrder.shippingAddress.street,
city: shopifyOrder.shippingAddress.city,
postalCode: shopifyOrder.shippingAddress.zip,
countryLetterCode: shopifyOrder.shippingAddress.countryCode
}
})
}
);
const order = await orderResponse.json();
// 2. Auftragspositionen hinzufügen
for (const item of shopifyOrder.lineItems) {
await fetch(
`${this.baseUrl}/api/v2.0/salesOrders(${order.id})/salesOrderLines`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
itemId: item.sku,
quantity: item.quantity,
unitPrice: item.unitPrice
})
}
);
}
return order;
}
}
Dynamics-spezifische Tipps:
- Verwenden Sie OAuth 2.0 Client Credentials Flow — kein Basic Auth
- Die API hat strenge Rate Limits (100 Requests/Min für OData), nutzen Sie
$batchfür Bulk-Operationen - Das Feld
externalDocumentNumberist ideal für die Shopify-Order-Nummer - Bei On-Premise-Installationen benötigen Sie ggf. einen Tunnel (z.B. Azure Relay)
JTL-Wawi
JTL ist im DACH-Raum weit verbreitet und bietet mit der JTL-Connector-API und der neueren REST API zwei Schnittstellen:
// JTL REST API: Bestellimport
async function importOrderToJTL(shopifyOrder) {
const jtlOrder = {
externalOrderId: shopifyOrder.name,
platformName: 'Shopify',
customer: {
email: shopifyOrder.email,
billingAddress: {
firstName: shopifyOrder.billing_address.first_name,
lastName: shopifyOrder.billing_address.last_name,
street: shopifyOrder.billing_address.address1,
postalCode: shopifyOrder.billing_address.zip,
city: shopifyOrder.billing_address.city,
countryIso: shopifyOrder.billing_address.country_code
}
},
items: shopifyOrder.line_items.map(item => ({
sku: item.sku,
name: item.title,
quantity: item.quantity,
priceGross: parseFloat(item.price),
taxRate: 19.0 // oder 7.0 für ermäßigt
})),
shippingMethod: shopifyOrder.shipping_lines?.[0]?.title || 'Standard',
paymentMethod: mapPaymentMethod(shopifyOrder.payment_gateway_names?.[0])
};
const response = await fetch(`${JTL_API_URL}/v1/orders`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${JTL_API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(jtlOrder)
});
return response.json();
}
Xentral
Xentral (ehemals Weclapp-Alternative, jetzt eigenständig) bietet eine saubere REST API:
// Xentral REST API: Auftrag anlegen
async function createXentralOrder(shopifyOrder) {
const response = await fetch(`${XENTRAL_URL}/api/v1/auftraege`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${XENTRAL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
kundennummer: await findOrCreateCustomer(shopifyOrder),
projekt: 'Shopify',
internet: shopifyOrder.name, // Shopify Order-Nr
positionen: shopifyOrder.line_items.map(item => ({
artikel: item.sku,
menge: item.quantity,
preis: parseFloat(item.price)
})),
lieferadresse: {
name: `${shopifyOrder.shipping_address.first_name} ${shopifyOrder.shipping_address.last_name}`,
strasse: shopifyOrder.shipping_address.address1,
plz: shopifyOrder.shipping_address.zip,
ort: shopifyOrder.shipping_address.city,
land: shopifyOrder.shipping_address.country_code
}
})
});
return response.json();
}
WeClapp
WeClapp bietet eine RESTful API mit guter Dokumentation:
// WeClapp: Auftrag anlegen
async function createWeClappOrder(shopifyOrder) {
const response = await fetch(`${WECLAPP_URL}/webapp/api/v1/salesOrder`, {
method: 'POST',
headers: {
'AuthenticationToken': WECLAPP_API_TOKEN,
'Content-Type': 'application/json'
},
body: JSON.stringify({
orderNumber: shopifyOrder.name,
customerId: await resolveCustomer(shopifyOrder.email),
orderItems: shopifyOrder.line_items.map(item => ({
articleNumber: item.sku,
quantity: item.quantity,
unitPrice: parseFloat(item.price),
taxId: getWeClappTaxId(item.tax_lines)
})),
deliveryAddress: {
firstName: shopifyOrder.shipping_address.first_name,
lastName: shopifyOrder.shipping_address.last_name,
street1: shopifyOrder.shipping_address.address1,
zipcode: shopifyOrder.shipping_address.zip,
city: shopifyOrder.shipping_address.city,
countryCode: shopifyOrder.shipping_address.country_code
}
})
});
return response.json();
}
Billbee
Billbee ist besonders bei kleineren Händlern beliebt und bietet eine pragmatische REST API:
// Billbee: Bestellung anlegen
async function createBillbeeOrder(shopifyOrder) {
const response = await fetch('https://app.billbee.io/api/v1/orders', {
method: 'POST',
headers: {
'X-Billbee-Api-Key': BILLBEE_API_KEY,
'Authorization': `Basic ${Buffer.from(
`${BILLBEE_USER}:${BILLBEE_PASSWORD}`
).toString('base64')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
orderNumber: shopifyOrder.name,
state: 1, // Neu
shippingAddress: {
firstName: shopifyOrder.shipping_address.first_name,
lastName: shopifyOrder.shipping_address.last_name,
street: shopifyOrder.shipping_address.address1,
zip: shopifyOrder.shipping_address.zip,
city: shopifyOrder.shipping_address.city,
countryCode: shopifyOrder.shipping_address.country_code
},
orderItems: shopifyOrder.line_items.map(item => ({
product: { sku: item.sku, title: item.title },
quantity: item.quantity,
totalPrice: parseFloat(item.price) * item.quantity
})),
paymentMethod: shopifyOrder.payment_gateway_names?.[0] === 'paypal'
? 2 : 1
})
});
return response.json();
}
Billbee-Tipp: Billbee hat bereits einen nativen Shopify-Connector. Nutzen Sie die API nur, wenn Sie Custom-Logik benötigen (z.B. Bestellungen vor dem Import filtern oder anreichern).
Fehlerbehandlung & Monitoring
Eine Integration, die nicht überwacht wird, ist wie ein Auto ohne Tankanzeige: Sie merken erst, dass etwas fehlt, wenn es zu spät ist.
Dead Letter Queue (DLQ)
Wenn ein Webhook oder eine Synchronisation nach mehreren Versuchen fehlschlägt, gehört die Nachricht in eine Dead Letter Queue — nicht ins Nirvana:
import { Queue, Worker } from 'bullmq';
// Haupt-Queue
const orderQueue = new Queue('shopify-orders');
// Dead Letter Queue
const dlq = new Queue('shopify-orders-dlq');
// Worker mit automatischer DLQ
const worker = new Worker('shopify-orders', async (job) => {
try {
await processOrderForERP(job.data);
} catch (error) {
if (job.attemptsMade >= job.opts.attempts - 1) {
// Letzter Versuch fehlgeschlagen → DLQ
await dlq.add('failed-order', {
originalJob: job.data,
error: error.message,
stack: error.stack,
failedAt: new Date().toISOString(),
attempts: job.attemptsMade + 1
});
// Alert auslösen
await sendAlert({
type: 'ORDER_SYNC_FAILED',
orderId: job.data.orderId,
orderName: job.data.orderName,
error: error.message,
severity: 'critical'
});
}
throw error; // Retry (wenn noch Versuche übrig)
}
}, {
connection: { host: 'localhost', port: 6379 },
concurrency: 5
});
Alerting
Setzen Sie Alerts für diese kritischen Szenarien:
| Szenario | Schweregrad | Kanal |
|---|---|---|
| Bestellung konnte nicht ins ERP übertragen werden | Kritisch | Slack + E-Mail + SMS |
| Bestandssync fehlgeschlagen (> 15 Min Verzögerung) | Hoch | Slack + E-Mail |
| Webhook-Endpunkt nicht erreichbar | Kritisch | E-Mail + SMS |
| API Rate Limit erreicht | Mittel | Slack |
| Dateninkonsistenz bei Reconciliation | Hoch | Slack + E-Mail |
| ERP-Verbindung unterbrochen | Kritisch | Alle Kanäle |
// Einfaches Alerting-System
async function sendAlert({ type, severity, orderId, error }) {
const message = `🚨 [${severity.toUpperCase()}] ${type}\n` +
`Order: ${orderId || 'N/A'}\n` +
`Error: ${error}\n` +
`Time: ${new Date().toISOString()}`;
// Slack Webhook
if (['critical', 'high'].includes(severity)) {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: message })
});
}
// E-Mail für kritische Alerts
if (severity === 'critical') {
await sendEmail({
to: process.env.ALERT_EMAIL,
subject: `[KRITISCH] ${type}`,
text: message
});
}
// Log immer
console.error(`ALERT: ${message}`);
}
Reconciliation (Datenabgleich)
Selbst mit perfekter Webhook-Architektur kann es zu Lücken kommen — ein verpasster Webhook, ein Edge Case in der Transformation, ein kurzzeitiger ERP-Ausfall. Deshalb ist ein täglicher Reconciliation-Job unverzichtbar:
// Täglicher Reconciliation-Job
async function dailyReconciliation() {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
// Alle Shopify-Bestellungen der letzten 24h
const shopifyOrders = await shopify.order.list({
created_at_min: yesterday.toISOString(),
status: 'any',
limit: 250
});
// Alle ERP-Aufträge der letzten 24h
const erpOrders = await erpClient.getOrders({
dateFrom: yesterday.toISOString(),
source: 'Shopify'
});
// Abgleich
const erpOrderNumbers = new Set(
erpOrders.map(o => o.externalOrderId)
);
const missingInERP = shopifyOrders.filter(
order => !erpOrderNumbers.has(order.name)
);
if (missingInERP.length > 0) {
console.error(
`RECONCILIATION: ${missingInERP.length} Bestellungen fehlen im ERP!`
);
for (const order of missingInERP) {
// Nachsynchronisieren
await orderQueue.add('reconcile-order', {
orderId: order.id,
orderName: order.name,
payload: order,
source: 'reconciliation'
}, { priority: 1 }); // Hohe Priorität
}
await sendAlert({
type: 'RECONCILIATION_MISMATCH',
severity: 'high',
error: `${missingInERP.length} Bestellungen fehlten im ERP und wurden nachsynchronisiert: ${missingInERP.map(o => o.name).join(', ')}`
});
} else {
console.log('RECONCILIATION: Alle Bestellungen korrekt synchronisiert ✓');
}
}
// Jeden Tag um 03:00 Uhr
// (via node-cron oder externem Scheduler)
Pro-Tipp: Der Reconciliation-Job sollte nicht nur Bestellungen prüfen, sondern auch Bestände, Fulfillments und Kundendaten. Implementieren Sie ihn schrittweise — Bestellungen zuerst, dann die anderen Domänen.
Kosten-Vergleich: Was kostet eine ERP-Integration?
Transparente Kostenaufstellung der drei Ansätze für einen Shop mit ca. 1.000 Bestellungen pro Monat:
Einmalkosten
| Ansatz | Setup / Entwicklung | Konfiguration | Schulung | Gesamt |
|---|---|---|---|---|
| Standard-Connector | 0 € | 500–1.500 € | 250 € | 750–1.750 € |
| iPaaS / Middleware | 0 € | 1.000–3.000 € | 500 € | 1.500–3.500 € |
| Custom Integration | 5.000–15.000 € | Inkl. | 500 € | 5.500–15.500 € |
Laufende Kosten (monatlich)
| Ansatz | Lizenz / SaaS | Hosting | Wartung | Gesamt/Monat |
|---|---|---|---|---|
| Standard-Connector | 50–200 € | 0 € | 0–100 € | 50–300 € |
| iPaaS / Middleware | 200–800 € | 0 € | 100–300 € | 300–1.100 € |
| Custom Integration | 0 € | 20–100 € | 200–500 € | 220–600 € |
Break-Even-Analyse
Standard-Connector:
Jahr 1: 1.250 € (Setup) + 2.100 € (12 × 175 €) = 3.350 €
Jahr 2: 2.100 €
Jahr 3: 2.100 €
─────────────────
3 Jahre Gesamt: 7.550 €
iPaaS / Middleware:
Jahr 1: 2.500 € (Setup) + 8.400 € (12 × 700 €) = 10.900 €
Jahr 2: 8.400 €
Jahr 3: 8.400 €
─────────────────
3 Jahre Gesamt: 27.700 €
Custom Integration:
Jahr 1: 10.000 € (Entwicklung) + 4.800 € (12 × 400 €) = 14.800 €
Jahr 2: 4.800 €
Jahr 3: 4.800 €
─────────────────
3 Jahre Gesamt: 24.400 €
Ergebnisse:
- Der Standard-Connector ist am günstigsten — wenn er Ihre Anforderungen erfüllt
- Die Custom Integration überholt den iPaaS-Ansatz bereits im 2. Jahr und spart langfristig Geld
- Bei stark wachsendem Bestellvolumen werden iPaaS-Kosten oft überproportional teurer (Preis pro Operation)
Wichtig: Diese Zahlen sind Richtwerte. Die tatsächlichen Kosten hängen von der Komplexität Ihres ERPs, der Anzahl der Datenströme und Ihrem Bestellvolumen ab. Für eine belastbare Kalkulation empfehle ich ein kostenloses 15-Minuten-Audit.
Red Flags bei der ERP-Integration
Wann wissen Sie, dass ein Standard-Connector oder iPaaS nicht mehr reicht? Hier sind die klaren Warnsignale:
1. Sie brauchen Custom-Geschäftslogik bei der Transformation
Ihr ERP erwartet Daten in einem Format, das kein Standard-Mapping abbilden kann:
- Staffelpreise, die abhängig vom Kundentyp, der Bestellmenge und dem Wochentag berechnet werden
- Komplexe Steuerlogik für B2B mit verschiedenen EU-Ländern und Reverse-Charge-Regeln
- Produktkonfigurationen (z.B. Gravuren, Maßanfertigungen), die als Line Item Properties in Shopify gespeichert sind und ins ERP als Stücklisten-Positionen übersetzt werden müssen
2. Sie haben mehr als ein ERP/WMS
Zwei Lager mit verschiedenen WMS-Systemen, ein ERP für die Buchhaltung und ein PIM für Produktdaten? Standard-Connectoren unterstützen in der Regel eine Datenquelle. Für Multi-System-Orchestrierung brauchen Sie entweder einen iPaaS mit Custom-Logik oder eine eigene Middleware.
3. Ihr Bestellvolumen übersteigt 2.000 Orders/Tag
Bei diesem Volumen werden API-Rate-Limits, Queue-Management und Parallelisierung kritisch. Standard-Connectoren mit 15-Minuten-Sync-Intervallen kommen hier an ihre Grenzen — Sie brauchen Echtzeit-Verarbeitung mit Backpressure-Handling.
4. Sie brauchen bidirektionale Echtzeit-Syncs
Nicht nur Bestellungen von Shopify ins ERP, sondern auch Preisänderungen, Produktupdates und Bestandskorrekturen vom ERP nach Shopify — und zwar in Echtzeit, nicht einmal pro Stunde.
5. Der Standard-Connector hat bereits 3+ Workarounds
Wenn Sie CSV-Exporte manuell bearbeiten, Zwischentabellen basteln oder Daten vor dem Import per Skript transformieren — dann haben Sie die Grenze des Connectors längst überschritten. Jeder Workaround ist eine potenzielle Fehlerquelle und Wartungslast.
6. Compliance-Anforderungen
GoBD-konforme Archivierung, DSGVO-konforme Kundendatenverarbeitung oder branchenspezifische Anforderungen (z.B. Chargenverfolgung in der Pharma-/Lebensmittelbranche) erfordern oft Custom-Logik, die kein Standardprodukt abdeckt.
7. Sie planen Multi-Channel-Expansion
Wenn neben Shopify auch Amazon, eBay, ein B2B-Portal und ein stationärer POS angebunden werden sollen, brauchen Sie eine zentrale Integrationsschicht, die alle Kanäle orchestriert. Das ist genau das Szenario, in dem eine Custom-Middleware glänzt.
Pro-Tipp: Wenn 3 oder mehr dieser Red Flags auf Sie zutreffen, ist eine Custom-Integration fast immer der richtige Weg. Aber selbst dann empfehle ich einen iterativen Ansatz: Starten Sie mit der Bestellsynchronisation, ergänzen Sie dann Bestände, dann Produkte. Nicht alles auf einmal. Mehr zu iterativer Automatisierung finden Sie in meinem Artikel über Shopify App-Entwicklung.
Checkliste: Vor dem Start der ERP-Integration
Bevor Sie mit der Integration beginnen, klären Sie diese Punkte:
Technisch
- API-Dokumentation des ERPs beschafft und geprüft — Gibt es eine REST API? Welche Version? Welche Authentifizierung?
- Shopify API-Zugang eingerichtet — Admin API Token mit den nötigen Scopes (read_orders, write_inventory, read_products, etc.)
- Testumgebung vorhanden — Shopify Development Store + ERP-Sandbox
- Netzwerk-Konnektivität geprüft — Kann Ihre Middleware das ERP erreichen? (On-Premise: VPN/Tunnel nötig!)
- Rate Limits dokumentiert — Sowohl Shopify als auch ERP
Organisatorisch
- Datenhoheit geklärt — Welches System ist Master für welche Daten?
- Mapping-Dokument erstellt — Welches Shopify-Feld wird in welches ERP-Feld geschrieben?
- Fallback-Prozesse definiert — Was passiert bei einem Ausfall? Wer wird benachrichtigt?
- Testdaten vorbereitet — Mindestens 20 repräsentative Bestellungen (inkl. Sonderfälle: B2B, international, Teillieferung)
- Rollback-Plan — Wie können Sie im Notfall auf den alten Prozess zurückschalten?
Monitoring
- Dashboard eingerichtet — Wie viele Syncs laufen? Wie viele schlagen fehl?
- Alerts konfiguriert — Wer wird bei Fehlern benachrichtigt?
- Reconciliation-Job geplant — Wann und wie oft wird abgeglichen?
Best Practices: 10 Regeln für eine stabile Integration
Aus über 30 ERP-Integrationsprojekten habe ich diese Regeln destilliert:
1. Immer idempotent implementieren. Jede Nachricht kann doppelt ankommen. Ihre Verarbeitung muss damit umgehen.
2. Absolute statt relative Bestandswerte. Setzen Sie Bestände auf den korrekten Wert, nicht "+5" oder "-3" — relative Werte akkumulieren Fehler.
3. SKU als universeller Identifier. Nicht die Shopify-ID, nicht die ERP-Artikelnummer. Die SKU ist die Brücke.
4. Webhooks sofort bestätigen, asynchron verarbeiten. Nie inline verarbeiten — immer in eine Queue schreiben.
5. Reconciliation ist Pflicht, nicht Optional. Mindestens ein täglicher Abgleich aller kritischen Daten.
6. Logging großzügig, Alerting gezielt. Loggen Sie jede Nachricht mit Context (Order-ID, SKU, Timestamps). Alerten Sie nur bei actionable Events.
7. Fehler kategorisieren. Nicht jeder Fehler ist gleich: Temporäre Netzwerkfehler → Retry. Ungültige Daten → DLQ + Alert. Auth-Fehler → Sofort alerten.
8. Feature-Flags für neue Sync-Richtungen. Wenn Sie eine neue Datendomäne (z.B. Retouren) einschalten, tun Sie das hinter einem Feature Flag — so können Sie bei Problemen sofort zurückrollen.
9. Rate Limiting respektieren. Implementieren Sie exponentielles Backoff und Throttling. Shopify schränkt bei Rate-Limit-Verletzungen den gesamten API-Zugang ein — hier finden Sie mehr zur API-Optimierung.
10. Dokumentation pflegen. Ein Mapping-Dokument, das erklärt, welches Feld wohin fließt, spart Ihnen Stunden beim Debugging.
Fazit: Der richtige Ansatz für Ihre Situation
Die ERP-Integration ist kein Projekt, das man einmal aufsetzt und dann vergisst. Es ist ein lebendiges System, das mit Ihrem Business wächst — neue Produkte, neue Märkte, neue Prozesse erfordern Anpassungen.
Schnelle Entscheidungshilfe
Wählen Sie einen Standard-Connector, wenn:
- Sie ein gängiges ERP (SAP, Navision, JTL) und Standard-Prozesse haben
- < 500 Bestellungen/Tag
- Budget unter 300 €/Monat
- Schnelle Time-to-Market wichtiger als Flexibilität
Wählen Sie iPaaS/Middleware, wenn:
- Sie 2–3 Systeme verbinden (ERP + PIM oder ERP + WMS)
- Moderate Anpassungen nötig sind (bedingte Logik, Transformationen)
- Internes Team die Flows pflegen soll
- 500–2.000 Bestellungen/Tag
Wählen Sie Custom Integration, wenn:
- Komplexe Geschäftslogik nicht in Standard-Flows abbildbar ist
-
2.000 Bestellungen/Tag oder starkes Wachstum erwartet wird
- Mehrere ERPs/WMS/PIM angebunden werden
- Langfristige Kosteneffizienz wichtiger ist als niedrige Startkosten
Nächster Schritt
Unsicher, welcher Ansatz für Ihr Setup der richtige ist? Ich analysiere Ihre aktuelle Systemlandschaft, identifiziere Quick Wins und erstelle einen konkreten Integrationsplan — in einem kostenlosen 15-Minuten-Audit.
👉 Kostenloses Audit vereinbaren
Oder schauen Sie sich an, wie Custom-Automatisierung in der Praxis aussieht: Shopify Automatisierung: 10 Prozesse, die kein Plugin automatisieren kann.

Über den Autor
Justin Kreutzmann ist Experte für Shopify-Entwicklung und E-Commerce-Skalierung. Er hilft Marken dabei, technische Grenzen zu überwinden und performante Online-Shops zu bauen.
Zusammenarbeiten →