Saltar al contenido principal
Volver al blog
Odoo Shopify Integración ERP E-commerce

Cómo integrar Odoo con Shopify: guía técnica completa

Guía paso a paso para sincronizar Odoo y Shopify: inventario, pedidos, clientes y contabilidad. Métodos, herramientas y ejemplos de código reales.

JM
Javier Manzano
CEO & Co-founder • 3 de junio de 2026

Tienes tu tienda en Shopify y tu gestión interna en Odoo. Dos sistemas que trabajan en paralelo, actualizaciones manuales de stock, pedidos que hay que copiar a mano, clientes duplicados en ambas bases de datos. Esto no es sostenible. La integración entre Odoo y Shopify es uno de los proyectos de automatización más demandados en el comercio electrónico B2C y B2B, y también uno de los que más matices técnicos requiere para hacerse bien.

Esta guía cubre todos los métodos disponibles, con ejemplos de código reales, los errores más comunes y la arquitectura recomendada para tiendas con volumen alto.

Por qué integrar Odoo con Shopify (y no gestionar por separado)

La gestión manual entre dos sistemas parece asequible hasta que escala. A partir de cierto volumen, los costes ocultos son enormes:

  • Errores de stock: Un producto vendido en Shopify que no descuenta en Odoo genera sobreventa. Un pedido devuelto que no se registra en Odoo genera stock fantasma.
  • Tiempo operativo: Actualizar precios, crear pedidos de venta, registrar clientes… Cada operación duplicada en dos sistemas es tiempo perdido.
  • Inconsistencia de datos: Los clientes tienen direcciones distintas en Shopify y en Odoo. La contabilidad no cuadra con las ventas reales.
  • Escalabilidad bloqueada: Con 50 pedidos al día puedes gestionarlo manualmente. Con 500, es imposible.

La integración resuelve todo esto: un único flujo de datos que mantiene ambos sistemas sincronizados en tiempo real (o casi real) sin intervención manual.

Qué datos necesitas sincronizar

Antes de elegir el método de integración, define exactamente qué flujos necesitas. Los más comunes son:

Inventario (Odoo → Shopify)

Odoo es la fuente de verdad del stock. Cada vez que cambia el stock en Odoo (por compra, venta, ajuste o devolución), Shopify debe actualizarse. La sincronización puede ser en tiempo real (webhook) o periódica (cada 5-15 minutos).

Pedidos (Shopify → Odoo)

Cada pedido confirmado en Shopify debe crear un pedido de venta en Odoo. Esto incluye líneas de producto, descuentos, impuestos, dirección de envío y datos del cliente.

Clientes (bidireccional)

Un cliente que compra por primera vez en Shopify debe crearse como partner en Odoo. Si ese cliente tiene historial en Odoo (por ejemplo, en un canal B2B previo), hay que evitar duplicados usando email o VAT como clave de deduplicación.

Precios y catálogo (Odoo → Shopify)

Si gestionas precios en Odoo (por listas de precios, tarifas B2B, descuentos por volumen), necesitas sincronizarlos hacia Shopify. Esta sincronización suele ser menos frecuente (diaria o por cambio explícito).

Contabilidad (Shopify → Odoo)

Los pagos, reembolsos y comisiones de Shopify Payments deben reflejarse en Odoo para que la contabilidad esté completa. Esto suele hacerse a través de asientos contables automáticos basados en los payouts de Shopify.

Método 1: API nativa de Odoo + Webhooks de Shopify

Este es el método más robusto y flexible. Requiere desarrollo pero te da control total sobre la lógica de sincronización.

Arquitectura del flujo

[Shopify] --webhook--> [Middleware API] --XML-RPC/REST--> [Odoo]
[Odoo]    --webhook--> [Middleware API] --Admin API-----> [Shopify]

El middleware (un servicio Node.js o Python) actúa como orquestador: recibe webhooks de ambos sistemas, transforma los datos y escribe en el otro sistema.

Recibir webhooks de Shopify en Python

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):
    """Verifica que el webhook realmente proviene de Shopify."""
    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():
    """Obtiene conexión autenticada a Odoo."""
    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():
    # Verificar firma
    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()

    # Buscar o crear cliente en Odoo
    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]

    # Crear pedido de venta en Odoo
    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'],  # Número de pedido Shopify
            'note': f"Pedido Shopify #{order['order_number']}",
        }]
    )

    # Confirmar el pedido automáticamente
    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):
    """Busca producto en Odoo por referencia interna (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

Sincronizar stock de Odoo a Shopify en Node.js

import axios from 'axios';

const SHOPIFY_STORE = process.env.SHOPIFY_STORE; // tutienda.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(`Producto ${sku} no tiene shopify_variant_id mapeado`);
    return;
  }

  // Obtener inventory_item_id desde Shopify
  const variantRes = await shopifyClient.get(
    `/variants/${shopify_variant_id}.json`
  );
  const inventoryItemId = variantRes.data.variant.inventory_item_id;

  // Actualizar stock
  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(`Stock actualizado: ${sku} → ${qty_available} unidades`);
  return response.data;
}

// Webhook de Odoo cuando cambia el stock (vía acción automatizada)
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} productos fallaron en la sincronización`);
  }

  res.json({ synced: results.length - failed.length, failed: failed.length });
});

La clave de este método es usar los **SKUs de producto** como identificador común entre Odoo y Shopify. El SKU (referencia interna en Odoo, `default_code`) debe ser el mismo en ambos sistemas. Antes de implementar cualquier integración, audita tu catálogo y asegúrate de que todos los productos tienen SKU único y consistente en los dos sistemas. Este paso previo evita el 80% de los problemas de sincronización.

Método 2: Conectores oficiales y del marketplace de Odoo

Existen módulos en el marketplace de Odoo Apps (apps.odoo.com) que ofrecen conectores preconfigurados para Shopify. Los más populares son:

  • Shopify Odoo Connector (varios proveedores como Emipro, Vraja Technologies): módulos que añaden una interfaz en Odoo para configurar la sincronización sin código.
  • Shopify Integration by OdooTec: solución popular con soporte para multi-tienda.

Pros de los conectores del marketplace

  • Tiempo de implementación reducido: Instalación y configuración en horas, no semanas.
  • Mantenimiento incluido: El proveedor actualiza el conector con cada versión de Odoo y los cambios de la API de Shopify.
  • Interfaz visual: No requiere que el equipo técnico gestione código de integración.
  • Escenarios cubiertos: La mayoría de los flujos estándar (pedidos, stock, clientes, precios) están contemplados.

Contras de los conectores del marketplace

  • Coste recurrente: Entre 150€ y 600€/año por licencia, más soporte.
  • Poca flexibilidad: Si tu lógica de negocio es particular (por ejemplo, reglas de precio complejas, múltiples almacenes, lógica de envío custom), los conectores estándar no siempre llegan.
  • Dependencia de terceros: Si el proveedor abandona el mantenimiento del módulo, tienes un problema.
  • Conflictos con otras personalizaciones: Los módulos de terceros a veces chocan entre sí o con módulos custom propios.

Los conectores del marketplace son ideales para tiendas con flujos estándar y volumen moderado (hasta ~500 pedidos/día). Para casos más complejos, la integración a medida suele ser mejor inversión a largo plazo.

Método 3: Middleware de automatización vs. código a medida

Make (antes Integromat) y Zapier

Herramientas como Make o Zapier permiten conectar Shopify y Odoo sin código mediante flujos visuales. Tienen conectores nativos para ambos sistemas.

Cuándo usar Make/Zapier:

  • Volumen bajo a medio (menos de 200 pedidos/día).
  • El equipo no tiene recursos de desarrollo.
  • Los flujos son simples y estándar (nuevo pedido → crear en Odoo, sin lógica compleja).
  • Fase de validación antes de invertir en integración a medida.

Cuándo NO usar Make/Zapier:

  • Más de 500 pedidos/día (los costes de ejecución escalan rápido).
  • Necesitas lógica compleja: deduplicación de clientes, transformaciones de datos, manejo de errores sofisticado.
  • Tienes requisitos de latencia bajos (sincronización en tiempo real de stock).
  • Necesitas trazabilidad completa y auditoría de cada operación.

Código a medida

Un servicio de integración propio (Python + FastAPI, Node.js + Express) te da control total. Es más caro de desarrollar inicialmente pero más barato a largo plazo para volúmenes altos, y mucho más flexible.

# Ejemplo: lógica de deduplicación de clientes más sofisticada
# que un conector estándar no suele incluir

def find_or_create_partner(models, uid, shopify_customer):
    """
    Estrategia de deduplicación por prioridad:
    1. Buscar por email (más fiable)
    2. Buscar por teléfono si no hay email
    3. Buscar por nombre + dirección si no hay email ni teléfono
    4. Crear si no existe ninguna coincidencia
    """
    email = shopify_customer.get('email', '').lower().strip()
    phone = shopify_customer.get('phone', '').strip()

    # Paso 1: buscar por email
    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'

    # Paso 2: buscar por teléfono
    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'

    # Paso 3: crear nuevo partner
    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 'Cliente sin nombre',
            'email': email,
            'phone': phone,
            'customer_rank': 1,
        }]
    )
    return new_id, 'created'

Errores comunes y cómo evitarlos

Stock duplicado o negativo

Problema: Si Shopify y Odoo ajustan el stock de forma independiente sin coordinación, puedes acabar con stock que no existe o con ventas de productos sin stock.

Solución: Establece una única fuente de verdad. En la mayoría de los casos, Odoo es el sistema maestro de inventario y Shopify solo refleja lo que Odoo dice. Nunca ajustes stock directamente en Shopify si tienes la integración activa.

Pedidos duplicados

Problema: Un webhook puede llegar dos veces (Shopify garantiza entrega at-least-once). Si no gestionas idempotencia, creates el pedido dos veces en Odoo.

Solución: Guarda el order_id de Shopify en Odoo (en el campo client_order_ref o en un campo custom) y verifica antes de crear:

def is_order_already_imported(models, uid, shopify_order_id):
    """Verifica si el pedido de Shopify ya fue importado a Odoo."""
    existing = models.execute_kw(
        ODOO_DB, uid, ODOO_PASSWORD,
        'sale.order', 'search_count',
        [[['client_order_ref', '=', f'shopify_{shopify_order_id}']]]
    )
    return existing > 0

IDs de producto desincronizados

Problema: Shopify usa variant_id y product_id. Odoo usa product.product (variante) y product.template (producto base). El mapeo no es trivial, especialmente con productos con múltiples variantes (talla, color).

Solución: Mantén una tabla de mapeo explícita. Puede ser tan simple como un campo custom en product.product de Odoo que almacene el shopify_variant_id:

# Añadir campo shopify_variant_id al modelo product.product en Odoo
# En un módulo custom (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,
    )

Fallos silenciosos en webhooks

Problema: Un webhook de Shopify falla (timeout, error 500) y el pedido nunca llega a Odoo. Shopify reintenta durante 48 horas pero si el problema persiste, se descarta.

Solución: Implementa un sistema de compensación: un proceso cron que cada hora consulta los pedidos de Shopify de las últimas 2 horas y verifica que todos existen en Odoo. Si falta alguno, lo importa.

// Proceso de reconciliación: cron cada hora
async function reconcileRecentOrders() {
  const oneHourAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();

  // Obtener pedidos recientes de Shopify
  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(`Pedido ${order.name} no encontrado en Odoo. Re-importando...`);
      await importOrderToOdoo(order);
    }
  }
}

Arquitectura recomendada para tiendas con más de 1000 pedidos/día

Con volumen alto, la arquitectura debe ser resiliente a picos, fallos parciales y retrasos de red:

[Shopify Webhooks]
        |
        v
[Cola de mensajes: Redis / RabbitMQ]
        |
        v
[Workers de procesamiento (múltiples instancias)]
        |
        +---> [Odoo XML-RPC / REST API]
        |
        +---> [Base de datos de auditoría]
        |
        v
[Cron de reconciliación (cada 1h)]
        |
        v
[Alertas: Slack / PagerDuty si hay discrepancias]

Principios clave:

  • Cola de mensajes: Los webhooks de Shopify escriben en una cola (Redis Streams, RabbitMQ). Los workers procesan de forma asíncrona. Esto evita que un pico de pedidos sature Odoo.
  • Workers escalables: Varios procesos worker que consumen la cola en paralelo. Escalan horizontalmente según la carga.
  • Idempotencia garantizada: Cada operación tiene un idempotency_key basado en el ID del evento de Shopify. Si el worker procesa el mismo evento dos veces, el resultado es el mismo.
  • Auditoría completa: Cada operación (webhook recibido, pedido creado, stock actualizado) se registra en una base de datos con timestamp, payload y resultado. Fundamental para debugging.
  • Circuit breaker: Si Odoo no responde durante más de N segundos, el worker deja de intentarlo y pone los mensajes en una cola de reintentos. Esto evita cascadas de fallos.

Checklist de implementación

Antes de poner en producción la integración, verifica cada punto:

Preparación del catálogo

  • Todos los productos tienen SKU único en Odoo (default_code)
  • Los SKUs de Odoo coinciden con los SKUs/referencias de Shopify
  • Los productos con variantes (talla, color) están correctamente mapeados variante a variante
  • La tabla de mapeo shopify_variant_id ↔ odoo_product_id está completa

Configuración técnica

  • Webhooks de Shopify configurados para: orders/create, orders/updated, orders/cancelled, refunds/create
  • Verificación de firma HMAC activa en todos los endpoints de webhook
  • Variables de entorno configuradas (nunca credenciales en el código)
  • Logs estructurados activados con nivel INFO o DEBUG en fase de pruebas

Flujos de datos

  • Nuevo pedido Shopify → pedido de venta en Odoo (probado con pedidos reales)
  • Cancelación en Shopify → cancelación en Odoo
  • Devolución en Shopify → devolución/nota de crédito en Odoo
  • Cambio de stock en Odoo → actualización en Shopify (probado con ajuste de inventario manual)
  • Nuevo cliente en Shopify → partner en Odoo (sin duplicados)

Resiliencia

  • Idempotencia verificada (procesar el mismo webhook dos veces no crea duplicados)
  • Proceso de reconciliación periódica activo
  • Alertas configuradas para fallos de sincronización
  • Plan de recuperación ante caída de Odoo (cola de mensajes aguanta sin perder datos)

Pruebas de carga

  • Simulado pico de pedidos (usar Shopify development store + datos de prueba)
  • Verificado que el tiempo de procesamiento de pedido < 30 segundos en condiciones normales

Cómo puede ayudarte Soamee

En Soamee somos especialistas en integraciones ERP y e-commerce. Hemos implementado integraciones Odoo-Shopify para clientes con volúmenes muy distintos, desde tiendas en crecimiento hasta operaciones con miles de pedidos diarios.

Nuestro enfoque siempre empieza por entender tu caso concreto: qué datos necesitas sincronizar, con qué frecuencia, qué lógica de negocio particular tienes (múltiples almacenes, diferentes tarifas por canal, gestión de bundles, dropshipping parcial). Solo entonces recomendamos el método adecuado, sea un conector estándar, un middleware a medida o una arquitectura basada en eventos.

Si estás valorando integrar Odoo con Shopify o tienes una integración existente que no funciona bien, agenda una consultoría gratuita. En 45 minutos podemos darte una hoja de ruta clara con opciones, estimación de esfuerzo y recomendación técnica.

Conclusión

La integración entre Odoo y Shopify no es un problema de “si” sino de “cómo”. El método correcto depende de tu volumen, la complejidad de tu lógica de negocio y tus recursos técnicos:

  • Conectores del marketplace: Ideal para empezar rápido con flujos estándar y volumen moderado.
  • Make/Zapier: Para validar la necesidad sin inversión en desarrollo, con volumen bajo.
  • Integración a medida con API: La opción más robusta y flexible para operaciones que escalan, con lógica propia y resiliencia real.

Sea cual sea el método que elijas, los pilares son los mismos: SKU como clave de mapeo, idempotencia en todos los flujos, fuente de verdad única para el stock, y un proceso de reconciliación que detecte y corrija discrepancias antes de que se conviertan en problemas para el cliente.

No te pierdas nada

JM

Javier Manzano

CEO & Co-founder en Soamee

Apasionado por la tecnología y el desarrollo de software. Comparto conocimientos y experiencias para ayudar a otros desarrolladores a crecer.

¿Te ha gustado este artículo?

Si necesitas ayuda con tu proyecto de desarrollo, estamos aquí para ti.

Agenda call gratuita →