This workflow corresponds to n8n.io template #14828 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "43c66d84-53a3-4af5-b2ca-bab4c3ed4952",
"name": "Shopify Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
0,
480
],
"parameters": {
"path": "shopify-order-webhook",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 1
},
{
"id": "e1d26e34-15db-41a6-8444-85c9bfe61cf3",
"name": "Split Line Items",
"type": "n8n-nodes-base.code",
"position": [
1344,
480
],
"parameters": {
"jsCode": "// ==================================================\n// Split Line Items - \u064a\u0641\u0631\u062f \u0627\u0644\u0645\u0646\u062a\u062c\u0627\u062a \u0639\u0634\u0627\u0646 \u0646\u0644\u0648\u0628 \u0639\u0644\u064a\u0647\u0645\n// ==================================================\nconst orderData = $('Parse Shopify Order1').first().json;\nconst lineItems = orderData.line_items || [];\nconst inputData = $input.first().json;\nconst partnerId = inputData.id;\n\nreturn lineItems.map((item, index) => ({\n json: {\n index: index,\n partner_id: partnerId,\n sku: item.sku || '',\n title: item.title,\n variant_title: item.variant_title || '',\n quantity: item.quantity,\n price_unit: item.price,\n total_discount: item.total_discount || 0,\n shopify_product_id: item.shopify_product_id,\n shopify_variant_id: item.shopify_variant_id,\n _order_meta: index === 0 ? {\n shopify_order_id: orderData.shopify_order_id,\n shopify_order_name: orderData.shopify_order_name,\n shipping_method: orderData.shipping_method,\n shipping_cost: orderData.shipping_cost,\n payment: orderData.payment,\n totals: orderData.totals,\n note: orderData.note\n } : null\n }\n}));"
},
"typeVersion": 2
},
{
"id": "2e844456-3d6c-49c1-8d09-8fbee2ce3d40",
"name": "Parse Shopify Order1",
"type": "n8n-nodes-base.code",
"position": [
224,
480
],
"parameters": {
"jsCode": "const order = $input.first().json.body || $input.first().json;\nconst customer = order.customer || {};\nconst shippingAddress = order.shipping_address || {};\n\nconst parsedOrder = {\n shopify_order_id: order.id,\n shopify_order_name: order.name,\n created_at: order.created_at,\n customer: {\n shopify_customer_id: customer.id || null,\n email: order.email || customer.email || '',\n full_name: ((customer.first_name || shippingAddress.first_name || '') + ' ' + \n (customer.last_name || shippingAddress.last_name || '')).trim(),\n phone: customer.phone || shippingAddress.phone || order.phone || ''\n },\n shipping: {\n name: shippingAddress.name || '',\n address1: shippingAddress.address1 || '',\n address2: shippingAddress.address2 || '',\n city: shippingAddress.city || '',\n province: shippingAddress.province || '',\n country: shippingAddress.country || '',\n country_code: shippingAddress.country_code || '',\n zip: shippingAddress.zip || '',\n phone: shippingAddress.phone || ''\n },\n shipping_method: (order.shipping_lines && order.shipping_lines.length > 0) \n ? order.shipping_lines[0].title : 'Standard Shipping',\n shipping_cost: (order.shipping_lines && order.shipping_lines.length > 0) \n ? parseFloat(order.shipping_lines[0].price) : 0,\n payment: {\n financial_status: order.financial_status || 'pending',\n payment_gateway: order.payment_gateway_names \n ? order.payment_gateway_names.join(', ') : '',\n currency: order.currency || 'USD'\n },\n totals: {\n subtotal: parseFloat(order.subtotal_price || 0),\n total: parseFloat(order.total_price || 0),\n total_tax: parseFloat(order.total_tax || 0),\n total_discounts: parseFloat(order.total_discounts || 0),\n discount_codes: order.discount_codes || []\n },\n line_items: (order.line_items || []).map(item => ({\n shopify_product_id: item.product_id,\n shopify_variant_id: item.variant_id,\n sku: item.sku || '',\n title: item.title || item.name || '',\n variant_title: item.variant_title || '',\n quantity: item.quantity || 1,\n price: parseFloat(item.price || 0),\n total_discount: parseFloat(item.total_discount || 0)\n })),\n note: order.note || '',\n tags: order.tags || ''\n};\n\nreturn [{ json: parsedOrder }];"
},
"typeVersion": 2
},
{
"id": "9764ad61-8a70-4bb3-a747-f9a505517672",
"name": "Customer Exists?1",
"type": "n8n-nodes-base.if",
"position": [
672,
480
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.id }}",
"operation": "isEmpty"
}
]
}
},
"typeVersion": 1
},
{
"id": "a38a10db-8899-416f-839e-9a09e3c5d018",
"name": "Get state id1",
"type": "n8n-nodes-base.odoo",
"position": [
896,
400
],
"parameters": {
"options": {},
"resource": "custom",
"operation": "getAll",
"returnAll": true,
"filterRequest": {
"filter": [
{
"value": "={{ $('Parse Shopify Order1').first().json.shipping.province }}",
"operator": "like",
"fieldName": "name"
}
]
},
"customResource": "res.country.state"
},
"typeVersion": 1
},
{
"id": "6014013c-0e34-4ace-8891-5a36fcaa5398",
"name": "Create Contact1",
"type": "n8n-nodes-base.odoo",
"position": [
1120,
400
],
"parameters": {
"resource": "custom",
"customResource": "res.partner",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "name",
"fieldValue": "={{ $('Parse Shopify Order1').first().json.customer.full_name }}"
},
{
"fieldName": "phone",
"fieldValue": "={{ $('Parse Shopify Order1').first().json.customer.phone }}"
},
{
"fieldName": "email",
"fieldValue": "={{ $('Parse Shopify Order1').first().json.customer.email }}"
},
{
"fieldName": "street",
"fieldValue": "={{ $('Parse Shopify Order1').first().json.shipping.address1 }}"
},
{
"fieldName": "city",
"fieldValue": "={{ $('Parse Shopify Order1').first().json.shipping.city }}"
},
{
"fieldName": "country_id",
"fieldValue": "65"
},
{
"fieldName": "lang",
"fieldValue": "ar_001"
},
{
"fieldName": "state_id",
"fieldValue": "={{ $json.id }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "815c259d-7413-4061-8b2f-2f7efdaa9653",
"name": "Search Product by Barcode1",
"type": "n8n-nodes-base.odoo",
"position": [
1568,
480
],
"parameters": {
"limit": 1,
"options": {
"fieldsList": [
"id",
"name",
"default_code",
"barcode",
"uom_id"
]
},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $json.sku }}",
"fieldName": "barcode"
}
]
},
"customResource": "product.product"
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "10c4f69b-aa09-4bc3-b4de-7df5c414921c",
"name": "Process Product Result1",
"type": "n8n-nodes-base.code",
"position": [
1792,
480
],
"parameters": {
"jsCode": "const allSearchResults = $input.all();\nconst allSplitItems = $('Split Line Items').all();\n\nconst barcodeMap = {};\nfor (const result of allSearchResults) {\n if (result.json && result.json.barcode) {\n barcodeMap[result.json.barcode] = result.json;\n }\n}\n\nconst results = [];\nfor (let i = 0; i < allSplitItems.length; i++) {\n const currentItem = allSplitItems[i].json;\n const matchedProduct = barcodeMap[currentItem.sku] || null;\n\n let productId = null;\n let productName = currentItem.title;\n\n if (matchedProduct && matchedProduct.id) {\n productId = matchedProduct.id;\n productName = matchedProduct.name || currentItem.title;\n }\n\n const unitPrice = currentItem.price_unit;\n const qty = currentItem.quantity;\n const totalDiscount = currentItem.total_discount || 0;\n const discountPercent = (qty > 0 && unitPrice > 0)\n ? (totalDiscount / (qty * unitPrice)) * 100\n : 0;\n\n results.push({\n json: {\n product_id: productId,\n product_name: productName,\n display_name: currentItem.variant_title\n ? currentItem.title + ' - ' + currentItem.variant_title\n : currentItem.title,\n quantity: currentItem.quantity,\n price_unit: currentItem.price_unit,\n discount: Math.round(discountPercent * 100) / 100,\n sku: currentItem.sku,\n partner_id: currentItem.partner_id,\n _order_meta: currentItem._order_meta\n }\n });\n}\nreturn results;"
},
"typeVersion": 2
},
{
"id": "aaa768f4-6678-4f19-a6a1-f52682cca678",
"name": "Aggregate Products1",
"type": "n8n-nodes-base.code",
"position": [
2016,
480
],
"parameters": {
"jsCode": "const allProducts = $input.all();\nconst partnerId = allProducts[0].json.partner_id;\n\nlet orderMeta = null;\nfor (const item of allProducts) {\n if (item.json._order_meta) {\n orderMeta = item.json._order_meta;\n break;\n }\n}\n\nconst foundProducts = allProducts.filter(item => item.json.product_id !== null);\nconst notFoundProducts = allProducts.filter(item => item.json.product_id === null);\n\nconst orderLines = foundProducts.map(item => {\n return [\n 0, 0, {\n product_id: item.json.product_id,\n name: item.json.display_name,\n product_uom_qty: item.json.quantity,\n price_unit: item.json.price_unit,\n discount: item.json.discount,\n tax_id: [[6, 0, []]]\n }\n ];\n});\n\nnotFoundProducts.forEach(item => {\n orderLines.push([\n 0, 0, {\n name: '[NOT FOUND] ' + item.json.display_name + ' (SKU: ' + item.json.sku + ')',\n product_uom_qty: item.json.quantity,\n price_unit: item.json.price_unit,\n discount: item.json.discount,\n tax_id: [[6, 0, []]]\n }\n ]);\n});\n\nreturn {\n json: {\n partner_id: partnerId,\n order_lines: orderLines,\n products_found: foundProducts.length,\n products_not_found: notFoundProducts.length,\n products_total: allProducts.length,\n order_meta: orderMeta\n }\n};"
},
"typeVersion": 2
},
{
"id": "44f3f25b-e198-45c1-b809-b5d0bff7758a",
"name": "Add Shipping to Order1",
"type": "n8n-nodes-base.code",
"position": [
2240,
480
],
"parameters": {
"jsCode": "const aggregatedData = $input.first().json;\nconst orderLines = aggregatedData.order_lines;\nconst partnerId = aggregatedData.partner_id;\nconst orderMeta = aggregatedData.order_meta || {};\n\nconst shippingCost = orderMeta.shipping_cost || 0;\nif (shippingCost > 0) {\n orderLines.push([\n 0, 0, {\n product_id: 16,\n name: '\u0645\u0635\u0627\u0631\u064a\u0641 \u0627\u0644\u0634\u062d\u0646',\n product_uom_qty: 1,\n price_unit: shippingCost,\n discount: 0,\n tax_id: [[6, 0, []]]\n }\n ]);\n}\n\nconst noteLines = [\n 'Shopify Order: ' + (orderMeta.shopify_order_name || ''),\n 'Shopify ID: ' + (orderMeta.shopify_order_id || ''),\n 'Payment: ' + (orderMeta.payment ? orderMeta.payment.financial_status + ' (' + orderMeta.payment.payment_gateway + ')' : ''),\n 'Shipping: ' + (orderMeta.shipping_method || ''),\n orderMeta.note ? 'Note: ' + orderMeta.note : ''\n].filter(l => l).join('\\n');\n\nreturn {\n json: {\n partner_id: partnerId,\n order_line: orderLines,\n client_order_ref: 'Shopify ' + (orderMeta.shopify_order_name || ''),\n origin: 'Shopify ' + (orderMeta.shopify_order_name || ''),\n note: noteLines,\n source_id: 17,\n products_found: aggregatedData.products_found,\n products_not_found: aggregatedData.products_not_found\n }\n};"
},
"typeVersion": 2
},
{
"id": "0d8e586b-74b9-4bde-9d1f-8279067fc59d",
"name": "Create Sales Order in Odoo1",
"type": "n8n-nodes-base.odoo",
"position": [
2464,
480
],
"parameters": {
"resource": "custom",
"customResource": "sale.order",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "partner_id",
"fieldValue": "={{ $json.partner_id }}"
},
{
"fieldName": "client_order_ref",
"fieldValue": "={{ $json.client_order_ref }}"
},
{
"fieldName": "origin",
"fieldValue": "={{ $json.origin }}"
},
{
"fieldName": "note",
"fieldValue": "={{ $json.note }}"
},
{
"fieldName": "source_id",
"fieldValue": "17"
},
{
"fieldName": "order_line",
"fieldValue": "={{ $('Add Shipping to Order1').first().json.order_line }}"
},
{
"fieldName": "pricelist_id",
"fieldValue": "9"
},
{
"fieldName": "user_id",
"fieldValue": "12"
}
]
}
},
"typeVersion": 1
},
{
"id": "78a6a1a0-6dc1-451e-9f3a-b264dfbe4911",
"name": "Search Customer in Odoo1",
"type": "n8n-nodes-base.odoo",
"position": [
448,
480
],
"parameters": {
"options": {
"fieldsList": [
"id"
]
},
"resource": "custom",
"operation": "getAll",
"returnAll": true,
"filterRequest": {
"filter": [
{
"value": "={{ $json.customer.phone }}",
"fieldName": "phone_mobile_search"
}
]
},
"customResource": "res.partner"
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "cfd1a050-15b8-401b-97f8-be59b8ea46e1",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-992,
112
],
"parameters": {
"width": 800,
"height": 618,
"content": "### How it works\nThis workflow automatically syncs new Shopify orders to Odoo as Sales Orders.\nIt parses the order payload, checks if the customer exists by phone number (creating them if not), matches products via SKU/Barcode, applies shipping costs, and generates a draft Sales Order in Odoo.\n\n### Setup steps\n1. **Webhook:** Add the Webhook URL to your Shopify Admin > Notifications > Webhooks (Order Creation).\n2. **Odoo Credentials:** Connect your Odoo account in n8n.\n3. **Shipping Product:** Open the \"Add Shipping to Order\" code node and update `product_id: 16` to match your Odoo Shipping service ID.\n4. **Sales Order Details:** Open the \"Create Sales Order\" node and adjust `pricelist_id`, `user_id`, and `source_id` to match your system."
},
"typeVersion": 1
},
{
"id": "1e90ad78-a573-4582-ba2d-2ff6b4184391",
"name": "Sticky Note 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
352
],
"parameters": {
"color": 7,
"width": 420,
"height": 250,
"content": "## 1. Receive & Parse Order\nCatches the incoming webhook payload from Shopify and uses custom code to extract and format only the required customer, shipping, and product details."
},
"typeVersion": 1
},
{
"id": "9abd446e-a6f3-412f-8e01-96c538242934",
"name": "Sticky Note 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
432,
288
],
"parameters": {
"color": 7,
"width": 850,
"height": 350,
"content": "## 2. Customer Lookup & Creation\nSearches Odoo for an existing customer using their phone number. If they don't exist, it resolves their State/Province ID and dynamically creates a new Contact profile before proceeding."
},
"typeVersion": 1
},
{
"id": "cb1588b8-5774-4bdd-8370-5cc72dd937cb",
"name": "Sticky Note 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1312,
352
],
"parameters": {
"color": 7,
"width": 1050,
"height": 250,
"content": "## 3. Product Matching & Order Build\nSplits the Shopify line items to search Odoo by SKU/Barcode individually. It then matches the results, calculates equivalent discounts, and aggregates everything back into a valid Odoo `order_line` payload format."
},
"typeVersion": 1
},
{
"id": "7f05ccff-2a64-4d94-9eae-8c0411c8258d",
"name": "Sticky Note 4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2400,
352
],
"parameters": {
"color": 7,
"width": 300,
"height": 250,
"content": "## 4. Finalize Order\nBuilds the final double-entry payload (including shipping costs and dynamic notes) and pushes the Draft Sales Order directly to your Odoo ERP."
},
"typeVersion": 1
}
],
"connections": {
"Get state id1": {
"main": [
[
{
"node": "Create Contact1",
"type": "main",
"index": 0
}
]
]
},
"Create Contact1": {
"main": [
[
{
"node": "Split Line Items",
"type": "main",
"index": 0
}
]
]
},
"Shopify Webhook": {
"main": [
[
{
"node": "Parse Shopify Order1",
"type": "main",
"index": 0
}
]
]
},
"Split Line Items": {
"main": [
[
{
"node": "Search Product by Barcode1",
"type": "main",
"index": 0
}
]
]
},
"Customer Exists?1": {
"main": [
[
{
"node": "Get state id1",
"type": "main",
"index": 0
}
],
[
{
"node": "Split Line Items",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Products1": {
"main": [
[
{
"node": "Add Shipping to Order1",
"type": "main",
"index": 0
}
]
]
},
"Parse Shopify Order1": {
"main": [
[
{
"node": "Search Customer in Odoo1",
"type": "main",
"index": 0
}
]
]
},
"Add Shipping to Order1": {
"main": [
[
{
"node": "Create Sales Order in Odoo1",
"type": "main",
"index": 0
}
]
]
},
"Process Product Result1": {
"main": [
[
{
"node": "Aggregate Products1",
"type": "main",
"index": 0
}
]
]
},
"Search Customer in Odoo1": {
"main": [
[
{
"node": "Customer Exists?1",
"type": "main",
"index": 0
}
]
]
},
"Search Product by Barcode1": {
"main": [
[
{
"node": "Process Product Result1",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
What Problem Does It Solve? Keeping product data consistent between Shopify and Odoo is a major operational challenge. Manually creating new products in Odoo every time they are added to Shopify is slow and leads to Barcode errors. Prices and titles change frequently; failing to…
Source: https://n8n.io/workflows/14828/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
📖 Description (WooCommerce Version)
This workflow functions like the integration of Shopify products with Odoo products, Shopify customers with Odoo customers, and paid Shopify orders with Odoo sales orders containing multiple products.
Shopify Order Data to Airtable
This workflow provides a robust, end-to-end automated pipeline for managing e-commerce orders. It bridges the gap between your storefront and your fulfillment team by handling inventory validation, mu
This n8n workflow helps Shopify store owners and teams automatically confirm orders via WhatsApp. It checks if the customer's number is valid using Rapiwa API, sends a personalized message, and logs e