Part of our eCommerce Integration series
Read the complete guideShopify + Odoo ERP Integration: The Complete Guide
As Shopify stores scale, the gap between what Shopify handles natively and what the business actually needs for operational excellence becomes a significant constraint. Inventory management across multiple warehouses, multi-currency accounting, manufacturing orders triggered by Shopify sales, sophisticated CRM with full customer history, and automated vendor replenishment — these require an ERP, and Odoo is increasingly the system of choice for mid-market ecommerce businesses.
Shopify + Odoo is not a plug-and-play integration. It requires careful architectural decisions about which system owns which data, what syncs in which direction, and how to handle edge cases like partial shipments, return processing, and variant mapping. This guide covers everything from integration architecture to specific implementation patterns.
Key Takeaways
- Odoo is the source of truth for inventory, customers, and accounting; Shopify is the source of truth for ecommerce transactions
- Bi-directional inventory sync (Shopify → Odoo for orders, Odoo → Shopify for stock levels) is the core integration requirement
- Product catalog management should live in Odoo and sync to Shopify — not the reverse
- Customer records merge between systems using email as the unique identifier
- Order lifecycle: Shopify creates order → Odoo receives order → Odoo creates delivery → fulfillment updates Shopify
- Return processing requires coordination: Shopify initiates return → Odoo processes receipt → both systems update
- Shopify's Webhook API provides real-time order events; Odoo receives these via connector middleware
- Direct Odoo-Shopify connectors exist (Syncee, OdooConnector) but custom integrations via REST API offer more control
Understanding the Integration Architecture
Before implementing, define the authoritative system for each data domain:
| Data Type | Authoritative System | Sync Direction | Frequency |
|---|---|---|---|
| Product catalog | Odoo | Odoo → Shopify | On product change |
| Inventory levels | Odoo | Odoo → Shopify | Real-time |
| Orders | Shopify | Shopify → Odoo | Real-time (webhook) |
| Customer records | Odoo (merged) | Bidirectional (email key) | On order |
| Pricing | Odoo | Odoo → Shopify | On price change |
| Shipping rates | Shopify | Shopify only | N/A |
| Payments | Shopify | Shopify → Odoo (accounting) | On settlement |
| Returns | Shopify initiated | Shopify → Odoo | On return creation |
Why Odoo owns inventory:
Shopify's inventory tracking is functional but limited for multi-warehouse, multi-channel operations. Odoo's inventory module handles: lot and serial number tracking, multi-location warehouse management, automated replenishment rules, manufacturing integration (finished goods decrement component inventory), and barcode-driven fulfillment operations. Shopify must reflect Odoo's inventory reality, not the other way around.
Why Shopify is the commerce layer:
Shopify's checkout, payment processing, shipping rate calculation, tax collection, and customer-facing experience are best-in-class. Odoo's B2C ecommerce (Odoo Website) is functional but not at Shopify's level for DTC commerce. The optimal architecture keeps Shopify as the commerce interface and Odoo as the operational backbone.
Integration Methods: Connector Apps vs. Custom API
Option 1: Pre-built connector apps
Several Shopify and Odoo marketplace apps provide pre-built integration:
| Connector | Approach | Monthly Cost | Pros | Cons |
|---|---|---|---|---|
| Zapiet + Odoo | Middleware via Zapier | $50-200 | Quick setup | Limited customization, single-point failure |
| Syncee | Direct connector | $29-99 | Catalog sync | Order processing limited |
| OdooConnector.com | Purpose-built | $200-500 | Comprehensive | Requires Odoo expertise |
| Eshop+ (Odoo App) | Odoo-native | Community free | Odoo-native | Basic Shopify support |
| Webkul Shopify Odoo | Purpose-built | Custom | Full lifecycle | Complex configuration |
Pre-built connectors work well for: standard product catalog and order sync for simple business models without complex variant structures, multiple warehouses, or manufacturing dependencies.
Option 2: Custom integration via APIs
For complex business requirements, a custom integration using Shopify's REST/GraphQL API and Odoo's JSON-RPC/REST API provides the most control and reliability.
Custom integration architecture:
Shopify (Commerce Layer)
│
│ Webhooks (orders/create, orders/updated, refunds/create, inventory_levels/update)
│
▼
Integration Service (Node.js / Python middleware)
│
│ Event processing, transformation, error handling, retry logic
│
▼
Odoo (ERP Layer)
│
│ Odoo JSON-RPC API (sales orders, inventory, customers, accounting)
│
└── Inventory updates → Shopify Admin API
Technology stack for custom integration:
- Middleware: Node.js (for Shopify ecosystem alignment) or Python (for Odoo ecosystem alignment)
- Queue: Redis or RabbitMQ for reliable event processing
- Database: PostgreSQL for integration state, idempotency keys, error logs
- Hosting: AWS Lambda or similar for webhook handlers (scales automatically with Shopify traffic spikes)
Order Sync: Shopify → Odoo
The order sync is the most critical integration path. Every Shopify order must create a corresponding Odoo sale order that triggers fulfillment and updates financial records.
Shopify webhook setup for order events:
// Register webhooks via Shopify API
const webhooks = [
{
topic: 'orders/create',
address: 'https://your-integration.com/webhooks/shopify/orders',
format: 'json'
},
{
topic: 'orders/updated',
address: 'https://your-integration.com/webhooks/shopify/orders/updated',
format: 'json'
},
{
topic: 'orders/fulfilled',
address: 'https://your-integration.com/webhooks/shopify/orders/fulfilled',
format: 'json'
},
{
topic: 'refunds/create',
address: 'https://your-integration.com/webhooks/shopify/refunds',
format: 'json'
}
];
Transforming a Shopify order to an Odoo sale order:
def shopify_order_to_odoo_sale_order(shopify_order: dict) -> dict:
"""Transform Shopify order payload to Odoo sale.order format"""
# Find or create Odoo partner (customer)
partner_id = find_or_create_odoo_partner(
email=shopify_order['email'],
name=shopify_order['customer']['first_name'] + ' ' + shopify_order['customer']['last_name'],
phone=shopify_order['customer'].get('phone'),
shipping_address=shopify_order['shipping_address']
)
# Map line items
order_lines = []
for item in shopify_order['line_items']:
odoo_product_id = get_odoo_product_from_shopify_variant(
item['variant_id']
)
order_lines.append({
'product_id': odoo_product_id,
'product_uom_qty': item['quantity'],
'price_unit': float(item['price']),
'name': item['name'],
'shopify_line_id': item['id'], # Custom field for traceability
})
# Add shipping as a service product line
if float(shopify_order.get('shipping_lines', [{}])[0].get('price', 0)) > 0:
order_lines.append({
'product_id': SHIPPING_PRODUCT_ID, # Configured in settings
'product_uom_qty': 1,
'price_unit': float(shopify_order['shipping_lines'][0]['price']),
'name': shopify_order['shipping_lines'][0]['title'],
})
return {
'partner_id': partner_id,
'order_line': [(0, 0, line) for line in order_lines],
'shopify_order_id': shopify_order['id'], # Custom field
'shopify_order_name': shopify_order['name'], # e.g., #1001
'note': shopify_order.get('note', ''),
'state': 'sale', # Confirm order automatically
}
Idempotency handling:
Shopify may deliver the same webhook event multiple times (network retries). Your integration must handle duplicate events gracefully:
def process_shopify_order_webhook(payload: dict):
shopify_order_id = str(payload['id'])
# Check if already processed
if OrderSyncLog.objects.filter(
shopify_order_id=shopify_order_id,
status='completed'
).exists():
logger.info(f"Order {shopify_order_id} already processed, skipping")
return
# Process and log
try:
odoo_order_id = create_odoo_sale_order(payload)
OrderSyncLog.objects.create(
shopify_order_id=shopify_order_id,
odoo_order_id=odoo_order_id,
status='completed'
)
except Exception as e:
OrderSyncLog.objects.create(
shopify_order_id=shopify_order_id,
status='failed',
error=str(e)
)
raise
Inventory Sync: Odoo → Shopify
Inventory levels must reflect Odoo's reality in Shopify in real-time (or near real-time) to prevent overselling.
Trigger-based inventory sync:
The most reliable approach is event-driven sync: when inventory changes in Odoo (sale, purchase receipt, manufacturing completion, stock adjustment), Odoo pushes the updated quantity to Shopify.
# In Odoo (using automated actions or override)
def _post_write_sync_to_shopify(self):
"""Called after inventory level changes in Odoo"""
for move_line in self:
product = move_line.product_id
location = move_line.location_id
if location.is_shopify_sync_location:
shopify_variant_id = product.shopify_variant_id
if shopify_variant_id:
new_quantity = product.with_context(
location=location.id
).qty_available
sync_inventory_to_shopify(
shopify_variant_id=shopify_variant_id,
quantity=int(new_quantity)
)
def sync_inventory_to_shopify(shopify_variant_id: str, quantity: int):
"""Push inventory update to Shopify via Admin API"""
inventory_item_id = get_inventory_item_id(shopify_variant_id)
location_id = get_shopify_location_id() # Primary Shopify location
shopify.InventoryLevel.set(
inventory_item_id=inventory_item_id,
location_id=location_id,
available=quantity
)
Scheduled inventory reconciliation:
Even with event-driven sync, schedule a daily full inventory reconciliation:
- Export all Odoo product quantities from the designated Shopify-sync location
- Compare against current Shopify inventory levels
- Update any discrepancies (may occur due to failed sync events, manual adjustments)
- Log reconciliation results for audit purposes
This reconciliation prevents inventory drift from accumulated small sync failures.
Product Catalog Sync: Odoo → Shopify
For businesses managing product catalogs in Odoo (with multi-currency pricing, detailed specifications, and complex variant matrices), syncing the catalog to Shopify eliminates manual double-entry.
Product mapping architecture:
Odoo Product (product.template)
├── Shopify Product (via shopify_product_id field on Odoo template)
│
└── Odoo Product Variants (product.product)
└── Shopify Variants (via shopify_variant_id field on Odoo product.product)
What to sync from Odoo to Shopify:
- Product name (Odoo's sales description)
- Product description (long HTML description)
- Images (product.template images)
- Price (using the configured Shopify pricelist)
- SKU (Odoo's internal reference)
- Barcode (EAN/UPC from Odoo)
- Weight (for shipping calculation)
- Active/archived status (unpublish in Shopify when Odoo product is archived)
- Inventory (from the designated sync location)
What NOT to sync from Odoo to Shopify:
- Shopify-specific SEO metadata (title tags, meta descriptions — manage in Shopify)
- Shopify product tags (manage in Shopify)
- Shopify collections/categories (manage in Shopify)
- Shopify-specific content (page builder sections, rich descriptions formatted for Shopify)
Customer Data Management
Customers who exist in both Shopify (from their storefront account) and Odoo (as contacts/partners) need careful merging to create a single unified profile.
Deduplication strategy using email:
def find_or_create_odoo_partner(email: str, name: str, **kwargs) -> int:
"""Find existing Odoo partner by email or create new one"""
existing = Partner.search([
('email', '=', email)
], limit=1)
if existing:
# Update with latest data from Shopify
existing.write({
'phone': kwargs.get('phone', existing.phone),
})
return existing.id
else:
# Create new partner
partner = Partner.create({
'name': name,
'email': email,
'phone': kwargs.get('phone'),
'type': 'contact',
'customer_rank': 1,
'shopify_customer_id': kwargs.get('shopify_customer_id'),
})
return partner.id
Shopify customer IDs stored in Odoo:
Add a custom field shopify_customer_id to Odoo's res.partner model. This enables bidirectional lookup: find Odoo partner from Shopify ID, find Shopify customer from Odoo partner.
Fulfillment Loop: Odoo → Shopify
When Odoo processes a delivery (picking + validate), the order is fulfilled. Shopify must be notified to:
- Mark the order as fulfilled
- Send the shipping confirmation email to the customer
- Record the tracking number
def sync_fulfillment_to_shopify(odoo_picking: StockPicking):
"""Called after Odoo delivery is validated"""
shopify_order_name = odoo_picking.sale_id.shopify_order_name
tracking_number = odoo_picking.carrier_tracking_ref
# Find Shopify order
shopify_orders = shopify.Order.find(name=shopify_order_name)
if not shopify_orders:
return
shopify_order = shopify_orders[0]
# Create fulfillment in Shopify
fulfillment = shopify.Fulfillment.create({
'order_id': shopify_order.id,
'tracking_number': tracking_number,
'tracking_company': odoo_picking.carrier_id.name,
'notify_customer': True, # Sends Shopify's shipping email
'line_items': [
{'id': line.shopify_line_id}
for line in odoo_picking.sale_id.order_line
if line.shopify_line_id
]
})
Accounting Integration: Shopify Sales → Odoo Financials
Every Shopify sale must eventually appear in Odoo's accounting module as a posted sale entry.
Integration approach for accounting:
Option 1 — Order-level accounting: Each Shopify order creates an Odoo invoice (or the Odoo sale order generates an invoice when fulfilled). Payments recorded in Shopify trigger payment registration in Odoo.
Option 2 — Settlement-level accounting: Shopify Payments settlements (daily or weekly bank deposits) are recorded in Odoo as journal entries that reconcile against bank transactions. This is simpler to maintain but provides less granular accounting.
For most mid-market merchants, settlement-level accounting (Option 2) is sufficient and significantly less complex to implement and maintain.
Shopify payout data → Odoo journal entry:
def process_shopify_payout(payout_data: dict):
"""Create Odoo journal entry for Shopify Payments payout"""
journal = ShopifyJournal.get_or_create() # Shopify clearing account
entry = AccountMove.create({
'journal_id': journal.id,
'date': payout_data['date'],
'ref': f"Shopify Payout {payout_data['id']}",
'line_ids': [
(0, 0, {
'account_id': SHOPIFY_CLEARING_ACCOUNT_ID,
'credit': payout_data['amount'],
'name': f"Shopify sales - {payout_data['period']}",
}),
(0, 0, {
'account_id': SHOPIFY_FEES_ACCOUNT_ID,
'debit': payout_data['fees'],
'name': 'Shopify Payments fees',
}),
(0, 0, {
'account_id': BANK_ACCOUNT_ID,
'debit': payout_data['amount'] - payout_data['fees'],
'name': f"Bank deposit - Shopify payout {payout_data['id']}",
}),
]
})
entry.action_post()
Frequently Asked Questions
How long does a Shopify-Odoo integration take to implement?
A basic integration (order sync, inventory sync, customer sync) using a pre-built connector takes 2-4 weeks including configuration, testing, and data migration. A custom integration covering the full lifecycle (orders, inventory, fulfillment sync, returns, accounting) takes 8-16 weeks depending on business complexity. Complex scenarios — multi-warehouse, manufacturing, multi-currency, multi-company Odoo — add 4-8 additional weeks. Budget for ongoing maintenance: integrations require updates when Shopify or Odoo release API changes.
Should I manage products in Shopify or Odoo?
For simple product catalogs: manage in Shopify and manually update Odoo for manufacturing/procurement purposes. For complex catalogs (many variants, multi-currency pricing, technical specifications, manufacturing BOMs): manage in Odoo and sync to Shopify. The critical factor is where your product team actually works. If your merchandising team lives in Shopify, forcing them to work in Odoo creates friction. If your operations team manages products in Odoo for manufacturing and procurement, Shopify sync is the right approach.
What happens to existing Shopify orders when the integration goes live?
Historical orders do not need to migrate to Odoo. The integration processes new orders from the go-live date forward. For historical data (customer records, product catalog, inventory baselines), perform a one-time data migration before the integration goes live: import historical customer data to Odoo, import product catalog, and set inventory baselines in Odoo to match the current Shopify quantities.
How do I handle Shopify orders with products that don't exist in Odoo?
This edge case breaks naive integrations. Build a fallback: when a Shopify order contains a variant ID that does not map to an Odoo product, create the order in Odoo with a placeholder "Unknown Product" product and alert your integration team. Define an error queue with notification: operations staff review unmapped products, create the Odoo product, and reprocess the failed order. This is preferable to silent failure or blocking all orders while awaiting a mapping fix.
Can this integration support multiple Shopify stores (e.g., different market stores)?
Yes, but with added complexity. Each Shopify store is a separate API connection. One Odoo instance can receive orders from multiple Shopify stores, with the store source tracked via a custom field. Inventory allocation between stores requires additional logic: either shared inventory pool (Odoo allocates across stores based on order demand) or location-segregated inventory (each store has a designated Odoo location). Multi-store integration doubles the testing scope and ongoing maintenance burden.
Next Steps
A well-implemented Shopify-Odoo integration transforms operational efficiency: eliminating manual data entry, preventing overselling, enabling sophisticated reporting, and connecting ecommerce sales to manufacturing, procurement, and financial processes.
ECOSIRE builds Shopify and Odoo ERP integrations for mid-market merchants — covering architecture design, custom development, data migration, testing, and ongoing support. Our integration team has implemented Shopify-Odoo connections for 30+ merchants across diverse product categories.
Contact our integration team to design your Shopify-Odoo integration architecture.
Written by
ECOSIRE Research and Development Team
Building enterprise-grade digital products at ECOSIRE. Sharing insights on Odoo integrations, e-commerce automation, and AI-powered business solutions.
Related Articles
Odoo Accounting vs QuickBooks: Detailed Comparison 2026
In-depth 2026 comparison of Odoo Accounting vs QuickBooks covering features, pricing, integrations, scalability, and which platform fits your business needs.
Case Study: eCommerce Migration to Shopify with Odoo Backend
How a fashion retailer migrated from WooCommerce to Shopify and connected it to Odoo ERP, cutting order fulfillment time by 71% and growing revenue 43%.
Case Study: Manufacturing ERP Implementation with Odoo 19
How a Pakistani auto-parts manufacturer cut order processing time by 68% and reduced inventory variance to under 2% with ECOSIRE's Odoo 19 implementation.
More from eCommerce Integration
Odoo eBay Connector: Listing, Orders, and Inventory Sync
Set up the Odoo eBay Connector for Odoo 19. Manage listings, automate order sync, synchronize inventory, handle returns, and manage multi-store eBay accounts from Odoo.
Managing Returns and Exchanges on Shopify
Complete guide to Shopify returns management: policy design, automated workflows, reverse logistics, exchange processing, and reducing return rates profitably.
Headless Shopify with Hydrogen: Build High-Performance Custom Storefronts
Complete guide to building headless Shopify storefronts with Hydrogen framework covering Remix, Storefront API, Oxygen hosting, and performance optimization.
Multi-Channel Inventory Synchronization: Preventing Stockouts and Overselling
Multi-channel inventory sync guide. Covers real-time synchronization methods, safety stock allocation, ERP integration, oversell prevention, and warehouse management.
Data Mapping & Transformation: Handling Different APIs & Data Formats
Master field mapping, data normalization, unit conversion, currency handling, and category taxonomy mapping across eCommerce APIs and data formats.
Headless Commerce Architecture: Decoupling Frontend from Backend
Compare headless vs monolithic commerce, explore API-first design with Shopify Storefront API, Next.js frontends, and modern commerce platform options.