Ihr Shop läuft auf Shopify, Ihre interne Verwaltung auf Odoo. Zwei Systeme, die parallel arbeiten: manuelle Bestandsaktualisierungen, Bestellungen, die von Hand kopiert werden müssen, doppelte Kunden in beiden Datenbanken. Das ist nicht nachhaltig. Die Integration zwischen Odoo und Shopify ist eines der meistgefragten Automatisierungsprojekte im B2C- und B2B-E-Commerce — und zugleich eines mit den meisten technischen Feinheiten, wenn man es richtig machen will.
Dieser Leitfaden behandelt alle verfügbaren Methoden, mit echten Code-Beispielen, den häufigsten Fehlern und der empfohlenen Architektur für Shops mit hohem Volumen.
Warum Odoo mit Shopify integrieren (statt getrennt zu verwalten)
Die manuelle Verwaltung zweier Systeme wirkt machbar — bis sie skaliert. Ab einem bestimmten Volumen sind die versteckten Kosten enorm:
- Bestandsfehler: Ein in Shopify verkauftes Produkt, das in Odoo nicht abgebucht wird, führt zu Überverkäufen. Eine Retoure, die in Odoo nicht erfasst wird, erzeugt Phantombestand.
- Operativer Zeitaufwand: Preise aktualisieren, Verkaufsaufträge anlegen, Kunden erfassen… Jede in zwei Systemen duplizierte Operation ist verlorene Zeit.
- Dateninkonsistenz: Kunden haben unterschiedliche Adressen in Shopify und in Odoo. Die Buchhaltung stimmt nicht mit den tatsächlichen Verkäufen überein.
- Blockierte Skalierbarkeit: Mit 50 Bestellungen pro Tag geht es noch manuell. Mit 500 ist es unmöglich.
Die Integration löst all das: ein einziger Datenfluss, der beide Systeme in Echtzeit (oder nahezu Echtzeit) synchron hält, ohne manuelle Eingriffe. Diese Art von Projekt ist ein typischer Fall von Odoo-Integration, wie wir sie bei Soamee umsetzen.
Welche Daten Sie synchronisieren müssen
Bevor Sie die Integrationsmethode wählen, definieren Sie genau, welche Datenflüsse Sie brauchen. Die häufigsten sind:
Lagerbestand (Odoo → Shopify)
Odoo ist die Single Source of Truth für den Bestand. Jedes Mal, wenn sich der Bestand in Odoo ändert (durch Einkauf, Verkauf, Anpassung oder Retoure), muss Shopify aktualisiert werden. Die Synchronisierung kann in Echtzeit (Webhook) oder periodisch (alle 5-15 Minuten) erfolgen.
Bestellungen (Shopify → Odoo)
Jede in Shopify bestätigte Bestellung muss einen Verkaufsauftrag in Odoo erzeugen. Dazu gehören Produktpositionen, Rabatte, Steuern, Lieferadresse und Kundendaten.
Kunden (bidirektional)
Ein Kunde, der zum ersten Mal in Shopify kauft, muss als Partner in Odoo angelegt werden. Hat dieser Kunde bereits eine Historie in Odoo (zum Beispiel aus einem früheren B2B-Kanal), müssen Duplikate vermieden werden — mit E-Mail oder USt-IdNr. als Deduplizierungsschlüssel.
Preise und Katalog (Odoo → Shopify)
Wenn Sie Preise in Odoo verwalten (über Preislisten, B2B-Tarife, Mengenrabatte), müssen Sie diese nach Shopify synchronisieren. Diese Synchronisierung erfolgt meist seltener (täglich oder bei expliziter Änderung).
Buchhaltung (Shopify → Odoo)
Zahlungen, Erstattungen und Gebühren von Shopify Payments müssen sich in Odoo widerspiegeln, damit die Buchhaltung vollständig ist. Das geschieht in der Regel über automatische Buchungssätze auf Basis der Shopify-Payouts.
Methode 1: Native Odoo-API + Shopify-Webhooks
Dies ist die robusteste und flexibelste Methode. Sie erfordert Entwicklung, gibt Ihnen aber volle Kontrolle über die Synchronisierungslogik.
Architektur des Datenflusses
[Shopify] --webhook--> [Middleware API] --XML-RPC/REST--> [Odoo]
[Odoo] --webhook--> [Middleware API] --Admin API-----> [Shopify]
Die Middleware (ein Node.js- oder Python-Service) fungiert als Orchestrator: Sie empfängt Webhooks beider Systeme, transformiert die Daten und schreibt in das jeweils andere System.
Shopify-Webhooks in Python empfangen
from flask import Flask, request, jsonify
import hmac
import hashlib
import base64
import xmlrpc.client
import os
app = Flask(__name__)
SHOPIFY_SECRET = os.environ['SHOPIFY_WEBHOOK_SECRET']
ODOO_URL = os.environ['ODOO_URL']
ODOO_DB = os.environ['ODOO_DB']
ODOO_USER = os.environ['ODOO_USER']
ODOO_PASSWORD = os.environ['ODOO_PASSWORD']
def verify_shopify_webhook(data, hmac_header):
"""Prüft, ob der Webhook tatsächlich von Shopify stammt."""
digest = hmac.new(
SHOPIFY_SECRET.encode('utf-8'),
data,
hashlib.sha256
).digest()
computed = base64.b64encode(digest).decode('utf-8')
return hmac.compare_digest(computed, hmac_header)
def get_odoo_connection():
"""Stellt eine authentifizierte Verbindung zu Odoo her."""
common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_PASSWORD, {})
models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')
return uid, models
@app.route('/webhooks/shopify/orders/create', methods=['POST'])
def shopify_order_created():
# Signatur prüfen
hmac_header = request.headers.get('X-Shopify-Hmac-Sha256')
if not verify_shopify_webhook(request.data, hmac_header):
return jsonify({'error': 'Unauthorized'}), 401
order = request.get_json()
uid, models = get_odoo_connection()
# Kunde in Odoo suchen oder anlegen
email = order['email']
partner_ids = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'res.partner', 'search',
[[['email', '=', email]]]
)
if not partner_ids:
partner_id = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'res.partner', 'create',
[{
'name': f"{order['billing_address']['first_name']} {order['billing_address']['last_name']}",
'email': email,
'phone': order['billing_address'].get('phone', ''),
'street': order['billing_address'].get('address1', ''),
'city': order['billing_address'].get('city', ''),
'zip': order['billing_address'].get('zip', ''),
'country_id': get_country_id(models, uid, order['billing_address'].get('country_code')),
}]
)
else:
partner_id = partner_ids[0]
# Verkaufsauftrag in Odoo anlegen
order_lines = []
for item in order['line_items']:
product_id = find_odoo_product(models, uid, item['sku'])
if product_id:
order_lines.append((0, 0, {
'product_id': product_id,
'product_uom_qty': item['quantity'],
'price_unit': float(item['price']),
'name': item['name'],
}))
sale_order_id = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'sale.order', 'create',
[{
'partner_id': partner_id,
'order_line': order_lines,
'client_order_ref': order['name'], # Shopify-Bestellnummer
'note': f"Shopify-Bestellung #{order['order_number']}",
}]
)
# Auftrag automatisch bestätigen
models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'sale.order', 'action_confirm',
[[sale_order_id]]
)
return jsonify({'odoo_order_id': sale_order_id}), 200
def find_odoo_product(models, uid, sku):
"""Sucht ein Produkt in Odoo anhand der internen Referenz (SKU)."""
product_ids = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'product.product', 'search',
[[['default_code', '=', sku]]]
)
return product_ids[0] if product_ids else None
Bestand von Odoo nach Shopify in Node.js synchronisieren
import axios from 'axios';
const SHOPIFY_STORE = process.env.SHOPIFY_STORE; // deinshop.myshopify.com
const SHOPIFY_TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
const shopifyClient = axios.create({
baseURL: `https://${SHOPIFY_STORE}/admin/api/2024-01`,
headers: {
'X-Shopify-Access-Token': SHOPIFY_TOKEN,
'Content-Type': 'application/json',
},
});
async function syncStockToShopify(odooProduct) {
const { sku, qty_available, shopify_variant_id, shopify_location_id } = odooProduct;
if (!shopify_variant_id) {
console.warn(`Produkt ${sku} hat keine zugeordnete shopify_variant_id`);
return;
}
// inventory_item_id von Shopify abrufen
const variantRes = await shopifyClient.get(
`/variants/${shopify_variant_id}.json`
);
const inventoryItemId = variantRes.data.variant.inventory_item_id;
// Bestand aktualisieren
const response = await shopifyClient.post('/inventory_levels/set.json', {
inventory_item_id: inventoryItemId,
location_id: shopify_location_id,
available: Math.max(0, Math.floor(qty_available)),
});
console.log(`Bestand aktualisiert: ${sku} → ${qty_available} Einheiten`);
return response.data;
}
// Odoo-Webhook bei Bestandsänderung (über automatisierte Aktion)
app.post('/webhooks/odoo/stock-update', async (req, res) => {
const { products } = req.body;
const results = await Promise.allSettled(
products.map(product => syncStockToShopify(product))
);
const failed = results.filter(r => r.status === 'rejected');
if (failed.length > 0) {
console.error(`${failed.length} Produkte sind bei der Synchronisierung fehlgeschlagen`);
}
res.json({ synced: results.length - failed.length, failed: failed.length });
});
Der Schlüssel dieser Methode liegt darin, die **Produkt-SKUs** als gemeinsamen Identifikator zwischen Odoo und Shopify zu verwenden. Die SKU (interne Referenz in Odoo, `default_code`) muss in beiden Systemen identisch sein. Bevor Sie irgendeine Integration implementieren, auditieren Sie Ihren Katalog und stellen Sie sicher, dass alle Produkte eine eindeutige und konsistente SKU in beiden Systemen haben. Dieser vorbereitende Schritt vermeidet 80% der Synchronisierungsprobleme.
Methode 2: Offizielle Konnektoren und der Odoo-Marketplace
Im Marketplace von Odoo Apps (apps.odoo.com) gibt es Module, die vorkonfigurierte Konnektoren für Shopify anbieten. Die beliebtesten sind:
- Shopify Odoo Connector (verschiedene Anbieter wie Emipro, Vraja Technologies): Module, die eine Oberfläche in Odoo hinzufügen, um die Synchronisierung ohne Code zu konfigurieren.
- Shopify Integration by OdooTec: beliebte Lösung mit Multi-Store-Unterstützung.
Vorteile der Marketplace-Konnektoren
- Reduzierte Implementierungszeit: Installation und Konfiguration in Stunden, nicht Wochen.
- Wartung inklusive: Der Anbieter aktualisiert den Konnektor mit jeder Odoo-Version und bei Änderungen der Shopify-API.
- Visuelle Oberfläche: Das technische Team muss keinen Integrationscode verwalten.
- Abgedeckte Szenarien: Die meisten Standardflüsse (Bestellungen, Bestand, Kunden, Preise) sind berücksichtigt.
Nachteile der Marketplace-Konnektoren
- Wiederkehrende Kosten: Zwischen 150€ und 600€/Jahr pro Lizenz, plus Support.
- Wenig Flexibilität: Wenn Ihre Geschäftslogik speziell ist (zum Beispiel komplexe Preisregeln, mehrere Lager, individuelle Versandlogik), reichen die Standard-Konnektoren nicht immer aus.
- Abhängigkeit von Dritten: Stellt der Anbieter die Wartung des Moduls ein, haben Sie ein Problem.
- Konflikte mit anderen Anpassungen: Module von Drittanbietern kollidieren manchmal untereinander oder mit eigenen Custom-Modulen.
Marketplace-Konnektoren sind ideal für Shops mit Standardflüssen und moderatem Volumen (bis ~500 Bestellungen/Tag). Für komplexere Fälle ist die maßgeschneiderte Integration langfristig meist die bessere Investition.
Methode 3: Automatisierungs-Middleware vs. individueller Code
Make (früher Integromat) und Zapier
Tools wie Make oder Zapier ermöglichen es, Shopify und Odoo ohne Code über visuelle Workflows zu verbinden. Beide haben native Konnektoren für beide Systeme.
Wann Make/Zapier sinnvoll ist:
- Niedriges bis mittleres Volumen (weniger als 200 Bestellungen/Tag).
- Das Team hat keine Entwicklungsressourcen.
- Die Workflows sind einfach und standardisiert (neue Bestellung → in Odoo anlegen, ohne komplexe Logik).
- Validierungsphase, bevor in eine maßgeschneiderte Integration investiert wird.
Wann Make/Zapier NICHT geeignet ist:
- Mehr als 500 Bestellungen/Tag (die Ausführungskosten skalieren schnell).
- Sie brauchen komplexe Logik: Kundendeduplizierung, Datentransformationen, ausgefeiltes Fehlerhandling.
- Sie haben niedrige Latenzanforderungen (Bestandssynchronisierung in Echtzeit).
- Sie brauchen vollständige Nachvollziehbarkeit und ein Audit jeder Operation.
Individueller Code
Ein eigener Integrationsservice (Python + FastAPI, Node.js + Express) gibt Ihnen volle Kontrolle. Er ist anfangs teurer in der Entwicklung, aber langfristig günstiger bei hohen Volumen — und deutlich flexibler.
# Beispiel: ausgefeiltere Kundendeduplizierungslogik,
# die ein Standard-Konnektor in der Regel nicht enthält
def find_or_create_partner(models, uid, shopify_customer):
"""
Deduplizierungsstrategie nach Priorität:
1. Suche per E-Mail (am zuverlässigsten)
2. Suche per Telefon, falls keine E-Mail vorhanden
3. Suche per Name + Adresse, falls weder E-Mail noch Telefon vorhanden
4. Anlegen, wenn keine Übereinstimmung existiert
"""
email = shopify_customer.get('email', '').lower().strip()
phone = shopify_customer.get('phone', '').strip()
# Schritt 1: Suche per E-Mail
if email:
ids = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'res.partner', 'search',
[[['email', '=ilike', email], ['active', 'in', [True, False]]]]
)
if ids:
return ids[0], 'found_by_email'
# Schritt 2: Suche per Telefon
if phone:
normalized_phone = ''.join(filter(str.isdigit, phone))[-9:]
ids = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'res.partner', 'search',
[[['phone', 'like', normalized_phone]]]
)
if ids:
return ids[0], 'found_by_phone'
# Schritt 3: neuen Partner anlegen
name = f"{shopify_customer.get('first_name', '')} {shopify_customer.get('last_name', '')}".strip()
new_id = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'res.partner', 'create',
[{
'name': name or 'Kunde ohne Namen',
'email': email,
'phone': phone,
'customer_rank': 1,
}]
)
return new_id, 'created'
Häufige Fehler und wie Sie sie vermeiden
Doppelter oder negativer Bestand
Problem: Wenn Shopify und Odoo den Bestand unabhängig voneinander und ohne Koordination anpassen, können Sie mit Bestand enden, der nicht existiert — oder mit Verkäufen von Produkten ohne Bestand.
Lösung: Legen Sie eine einzige Source of Truth fest. In den meisten Fällen ist Odoo das Master-System für den Bestand, und Shopify spiegelt nur wider, was Odoo sagt. Passen Sie den Bestand niemals direkt in Shopify an, wenn die Integration aktiv ist.
Doppelte Bestellungen
Problem: Ein Webhook kann zweimal ankommen (Shopify garantiert At-least-once-Zustellung). Ohne Idempotenz legen Sie die Bestellung zweimal in Odoo an.
Lösung: Speichern Sie die Shopify-order_id in Odoo (im Feld client_order_ref oder in einem Custom-Feld) und prüfen Sie vor dem Anlegen:
def is_order_already_imported(models, uid, shopify_order_id):
"""Prüft, ob die Shopify-Bestellung bereits in Odoo importiert wurde."""
existing = models.execute_kw(
ODOO_DB, uid, ODOO_PASSWORD,
'sale.order', 'search_count',
[[['client_order_ref', '=', f'shopify_{shopify_order_id}']]]
)
return existing > 0
Nicht synchronisierte Produkt-IDs
Problem: Shopify verwendet variant_id und product_id. Odoo verwendet product.product (Variante) und product.template (Basisprodukt). Das Mapping ist nicht trivial, besonders bei Produkten mit mehreren Varianten (Größe, Farbe).
Lösung: Pflegen Sie eine explizite Mapping-Tabelle. Das kann so einfach sein wie ein Custom-Feld in product.product in Odoo, das die shopify_variant_id speichert:
# Feld shopify_variant_id zum Modell product.product in Odoo hinzufügen
# In einem Custom-Modul (models/product.py):
from odoo import models, fields
class ProductProduct(models.Model):
_inherit = 'product.product'
shopify_variant_id = fields.Char(
string='Shopify Variant ID',
index=True,
copy=False,
)
shopify_product_id = fields.Char(
string='Shopify Product ID',
index=True,
copy=False,
)
Stille Fehler bei Webhooks
Problem: Ein Shopify-Webhook schlägt fehl (Timeout, Fehler 500) und die Bestellung kommt nie in Odoo an. Shopify wiederholt den Versand 48 Stunden lang, aber wenn das Problem bestehen bleibt, wird der Webhook verworfen.
Lösung: Implementieren Sie ein Kompensationssystem: einen Cron-Prozess, der jede Stunde die Shopify-Bestellungen der letzten 2 Stunden abfragt und prüft, ob alle in Odoo existieren. Fehlt eine, wird sie importiert.
// Abgleichprozess: Cron jede Stunde
async function reconcileRecentOrders() {
const oneHourAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
// Aktuelle Bestellungen von Shopify abrufen
const { data } = await shopifyClient.get('/orders.json', {
params: {
created_at_min: oneHourAgo,
status: 'any',
limit: 250,
},
});
for (const order of data.orders) {
const exists = await checkOrderInOdoo(order.id);
if (!exists) {
console.warn(`Bestellung ${order.name} nicht in Odoo gefunden. Erneuter Import...`);
await importOrderToOdoo(order);
}
}
}
Empfohlene Architektur für Shops mit mehr als 1000 Bestellungen/Tag
Bei hohem Volumen muss die Architektur resilient gegenüber Lastspitzen, Teilausfällen und Netzwerkverzögerungen sein:
[Shopify Webhooks]
|
v
[Message Queue: Redis / RabbitMQ]
|
v
[Verarbeitungs-Worker (mehrere Instanzen)]
|
+---> [Odoo XML-RPC / REST API]
|
+---> [Audit-Datenbank]
|
v
[Abgleich-Cron (stündlich)]
|
v
[Alerts: Slack / PagerDuty bei Diskrepanzen]
Zentrale Prinzipien:
- Message Queue: Die Shopify-Webhooks schreiben in eine Queue (Redis Streams, RabbitMQ). Die Worker verarbeiten asynchron. So verhindert man, dass ein Bestellpeak Odoo überlastet.
- Skalierbare Worker: Mehrere Worker-Prozesse konsumieren die Queue parallel. Sie skalieren horizontal je nach Last.
- Garantierte Idempotenz: Jede Operation hat einen
idempotency_keyauf Basis der Shopify-Event-ID. Verarbeitet der Worker dasselbe Event zweimal, ist das Ergebnis identisch. - Vollständiges Audit: Jede Operation (empfangener Webhook, angelegte Bestellung, aktualisierter Bestand) wird mit Timestamp, Payload und Ergebnis in einer Datenbank protokolliert. Unverzichtbar fürs Debugging.
- Circuit Breaker: Antwortet Odoo länger als N Sekunden nicht, stoppt der Worker die Versuche und legt die Nachrichten in eine Retry-Queue. Das verhindert kaskadierende Ausfälle.
Implementierungs-Checkliste
Bevor Sie die Integration in Produktion nehmen, prüfen Sie jeden Punkt:
Katalogvorbereitung
- Alle Produkte haben eine eindeutige SKU in Odoo (
default_code) - Die Odoo-SKUs stimmen mit den SKUs/Referenzen in Shopify überein
- Produkte mit Varianten (Größe, Farbe) sind korrekt Variante für Variante gemappt
- Die Mapping-Tabelle
shopify_variant_id ↔ odoo_product_idist vollständig
Technische Konfiguration
- Shopify-Webhooks konfiguriert für:
orders/create,orders/updated,orders/cancelled,refunds/create - HMAC-Signaturprüfung an allen Webhook-Endpoints aktiv
- Umgebungsvariablen konfiguriert (niemals Zugangsdaten im Code)
- Strukturierte Logs aktiviert, mit Level INFO oder DEBUG in der Testphase
Datenflüsse
- Neue Shopify-Bestellung → Verkaufsauftrag in Odoo (mit echten Bestellungen getestet)
- Stornierung in Shopify → Stornierung in Odoo
- Retoure in Shopify → Retoure/Gutschrift in Odoo
- Bestandsänderung in Odoo → Aktualisierung in Shopify (mit manueller Inventuranpassung getestet)
- Neuer Kunde in Shopify → Partner in Odoo (ohne Duplikate)
Resilienz
- Idempotenz verifiziert (die doppelte Verarbeitung desselben Webhooks erzeugt keine Duplikate)
- Periodischer Abgleichprozess aktiv
- Alerts für Synchronisierungsfehler konfiguriert
- Wiederherstellungsplan bei Odoo-Ausfall (Message Queue puffert ohne Datenverlust)
Lasttests
- Bestellpeak simuliert (Shopify Development Store + Testdaten verwenden)
- Verifiziert, dass die Verarbeitungszeit pro Bestellung unter normalen Bedingungen < 30 Sekunden liegt
Wie Soamee Ihnen helfen kann
Bei Soamee sind wir Spezialisten für ERP- und E-Commerce-Integrationen. Wir haben Odoo-Shopify-Integrationen für Kunden mit sehr unterschiedlichen Volumen umgesetzt — von wachsenden Shops bis zu Operationen mit Tausenden von Bestellungen täglich.
Unser Ansatz beginnt immer damit, Ihren konkreten Fall zu verstehen: welche Daten Sie synchronisieren müssen, wie häufig, und welche spezielle Geschäftslogik Sie haben (mehrere Lager, unterschiedliche Tarife pro Kanal, Bundle-Verwaltung, partielles Dropshipping). Erst dann empfehlen wir die passende Methode — sei es ein Standard-Konnektor, eine maßgeschneiderte Middleware oder eine ereignisbasierte Architektur.
Wenn Sie ein maßgeschneidertes ERP benötigen oder eine Integration von Odoo mit Shopify in Erwägung ziehen, vereinbaren Sie eine kostenlose Beratung. In 45 Minuten können wir Ihnen eine klare Roadmap mit Optionen, Aufwandsschätzung und technischer Empfehlung geben.
Fazit
Die Integration zwischen Odoo und Shopify ist keine Frage des “Ob”, sondern des “Wie”. Die richtige Methode hängt von Ihrem Volumen, der Komplexität Ihrer Geschäftslogik und Ihren technischen Ressourcen ab:
- Marketplace-Konnektoren: Ideal für einen schnellen Start mit Standardflüssen und moderatem Volumen.
- Make/Zapier: Um den Bedarf ohne Entwicklungsinvestition zu validieren, bei niedrigem Volumen.
- Maßgeschneiderte API-Integration: Die robusteste und flexibelste Option für skalierende Operationen, mit eigener Logik und echter Resilienz.
Welche Methode Sie auch wählen, die Grundpfeiler sind dieselben: SKU als Mapping-Schlüssel, Idempotenz in allen Datenflüssen, eine einzige Source of Truth für den Bestand und ein Abgleichprozess, der Diskrepanzen erkennt und korrigiert, bevor sie zu Problemen für den Kunden werden.