Tem a sua loja no Shopify e a gestão interna no Odoo. Dois sistemas a correr em paralelo, atualizações manuais de stock, pedidos que têm de ser copiados à mão, clientes duplicados em ambas as bases de dados. Isto não é sustentável. A integração entre Odoo e Shopify é um dos projetos de automação mais procurados no comércio eletrónico B2C e B2B — e também um dos que exige mais cuidado técnico para ser feito corretamente.
Este guia cobre todos os métodos disponíveis com exemplos de código reais, os erros mais comuns e a arquitetura recomendada para lojas com grande volume.
Por que integrar Odoo com Shopify (e não gerir separadamente)
A gestão manual entre dois sistemas parece viável até crescer. A partir de certo volume, os custos ocultos são enormes:
- Erros de stock: Um produto vendido no Shopify que não desconta no Odoo gera sobrevenda. Uma devolução não registada no Odoo cria stock fantasma.
- Tempo operacional: Atualizar preços, criar pedidos de venda, registar clientes — cada operação duplicada em dois sistemas é tempo perdido.
- Inconsistência de dados: Os clientes têm endereços diferentes no Shopify e no Odoo. A contabilidade não corresponde às vendas reais.
- Escalabilidade bloqueada: Com 50 pedidos por dia consegue gerir manualmente. Com 500, é impossível.
A integração resolve tudo isso: um único fluxo de dados que mantém ambos os sistemas sincronizados em tempo real (ou quase real) sem intervenção manual.
Que dados precisa de sincronizar
Antes de escolher o método de integração, defina exatamente quais os fluxos de que necessita. Os mais comuns são:
Inventário (Odoo → Shopify)
O Odoo é a fonte de verdade do stock. Sempre que o stock muda no Odoo (compra, venda, ajuste ou devolução), o Shopify deve ser atualizado. A sincronização pode ser em tempo real (webhook) ou periódica (a cada 5–15 minutos).
Pedidos (Shopify → Odoo)
Cada pedido confirmado no Shopify deve criar um pedido de venda no Odoo. Isso inclui linhas de produto, descontos, impostos, morada de envio e dados do cliente.
Clientes (bidirecional)
Um cliente que compra pela primeira vez no Shopify deve ser criado como parceiro no Odoo. Se esse cliente já tiver historial no Odoo (por exemplo, através de um canal B2B anterior), é necessário evitar duplicados usando o email ou o NIF como chave de deduplicação.
Preços e catálogo (Odoo → Shopify)
Se gerir preços no Odoo (através de listas de preços, tarifas B2B, descontos por volume), precisa de os enviar para o Shopify. Esta sincronização é normalmente menos frequente (diária ou por alteração explícita).
Contabilidade (Shopify → Odoo)
Os pagamentos, reembolsos e comissões do Shopify Payments devem ser refletidos no Odoo para que a contabilidade esteja completa. Isto é feito normalmente através de lançamentos contabilísticos automáticos baseados nos payouts do Shopify.
Método 1: API nativa do Odoo + Webhooks do Shopify
Este é o método mais robusto e flexível. Requer desenvolvimento mas dá-lhe controlo total sobre a lógica de sincronização.
Arquitetura do fluxo
[Shopify] --webhook--> [Middleware API] --XML-RPC/REST--> [Odoo]
[Odoo] --webhook--> [Middleware API] --Admin API-----> [Shopify]
O middleware (um serviço Node.js ou Python) atua como orquestrador: recebe webhooks de ambos os sistemas, transforma os dados e escreve no outro sistema.
Receber webhooks do Shopify em 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 o webhook provém genuinamente do 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():
"""Devolve uma ligação autenticada ao 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 assinatura
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()
# Encontrar ou criar cliente no 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]
# Criar pedido de venda no 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 do pedido Shopify
'note': f"Pedido Shopify #{order['order_number']}",
}]
)
# Confirmar o pedido automaticamente
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):
"""Procura um produto no Odoo pela referência 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 do Odoo para o Shopify em Node.js
import axios from 'axios';
const SHOPIFY_STORE = process.env.SHOPIFY_STORE; // sualoja.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(`Produto ${sku} não tem shopify_variant_id mapeado`);
return;
}
// Obter inventory_item_id do Shopify
const variantRes = await shopifyClient.get(
`/variants/${shopify_variant_id}.json`
);
const inventoryItemId = variantRes.data.variant.inventory_item_id;
// Atualizar 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 atualizado: ${sku} → ${qty_available} unidades`);
return response.data;
}
// Webhook do Odoo quando o stock muda (via ação 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} produtos falharam na sincronização`);
}
res.json({ synced: results.length - failed.length, failed: failed.length });
});
A chave deste método é usar os **SKUs de produto** como identificador comum entre o Odoo e o Shopify. O SKU (referência interna no Odoo, `default_code`) deve ser igual em ambos os sistemas. Antes de implementar qualquer integração, audite o seu catálogo e certifique-se de que todos os produtos têm SKU único e consistente nos dois sistemas. Este passo preparatório evita 80% dos problemas de sincronização.
Método 2: Conectores oficiais e do marketplace do Odoo
Existem módulos no marketplace do Odoo Apps (apps.odoo.com) que oferecem conectores pré-configurados para o Shopify. Os mais populares incluem:
- Shopify Odoo Connector (fornecedores como Emipro, Vraja Technologies): módulos que adicionam uma interface dentro do Odoo para configurar a sincronização sem código.
- Shopify Integration by OdooTec: solução popular com suporte a múltiplas lojas.
Vantagens dos conectores do marketplace
- Tempo de implementação reduzido: Instalação e configuração em horas, não semanas.
- Manutenção incluída: O fornecedor atualiza o conector com cada nova versão do Odoo e as alterações da API do Shopify.
- Interface visual: Não requer que a equipa técnica gira código de integração.
- Cenários cobertos: A maioria dos fluxos padrão (pedidos, stock, clientes, preços) está contemplada de raiz.
Desvantagens dos conectores do marketplace
- Custo recorrente: Entre 150€ e 600€/ano por licença, mais suporte.
- Pouca flexibilidade: Se a sua lógica de negócio for específica (regras de preço complexas, múltiplos armazéns, lógica de envio personalizada), os conectores padrão muitas vezes ficam aquém.
- Dependência de terceiros: Se o fornecedor deixar de manter o módulo, tem um problema.
- Conflitos com outras personalizações: Módulos de terceiros por vezes colidem entre si ou com os seus próprios módulos personalizados.
Os conectores do marketplace são ideais para lojas com fluxos padrão e volume moderado (até ~500 pedidos/dia). Para casos mais complexos, uma integração à medida é normalmente um melhor investimento a longo prazo.
Método 3: Middleware de automação vs. código à medida
Make (anteriormente Integromat) e Zapier
Ferramentas como Make ou Zapier permitem ligar o Shopify ao Odoo sem código através de fluxos visuais. Têm conectores nativos para ambos os sistemas.
Quando usar Make/Zapier:
- Volume baixo a médio (menos de 200 pedidos/dia).
- A equipa não tem recursos de desenvolvimento.
- Os fluxos são simples e padrão (novo pedido → criar no Odoo, sem lógica complexa).
- Fase de validação antes de investir numa integração à medida.
Quando NÃO usar Make/Zapier:
- Mais de 500 pedidos/dia (os custos de execução escalam rapidamente).
- Precisa de lógica complexa: deduplicação de clientes, transformações de dados, tratamento sofisticado de erros.
- Tem requisitos de baixa latência (sincronização de stock em tempo real).
- Precisa de rastreabilidade completa e auditoria de cada operação.
Código à medida
Um serviço de integração próprio (Python + FastAPI, Node.js + Express) dá-lhe controlo total. É mais caro de desenvolver inicialmente mas mais barato a longo prazo para volumes elevados, e muito mais flexível.
# Exemplo: lógica de deduplicação de clientes mais sofisticada
# que um conector padrão normalmente não inclui
def find_or_create_partner(models, uid, shopify_customer):
"""
Estratégia de deduplicação por prioridade:
1. Procurar por email (mais fiável)
2. Procurar por telefone se não houver email
3. Procurar por nome + morada se não houver email nem telefone
4. Criar se não houver correspondência
"""
email = shopify_customer.get('email', '').lower().strip()
phone = shopify_customer.get('phone', '').strip()
# Passo 1: procurar 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'
# Passo 2: procurar por telefone
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'
# Passo 3: criar novo parceiro
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 sem nome',
'email': email,
'phone': phone,
'customer_rank': 1,
}]
)
return new_id, 'created'
Erros comuns e como evitá-los
Stock duplicado ou negativo
Problema: Se o Shopify e o Odoo ajustam o stock de forma independente sem coordenação, pode acabar com stock que não existe ou com vendas de produtos sem stock.
Solução: Estabeleça uma única fonte de verdade. Na maioria dos casos o Odoo é o sistema mestre de inventário e o Shopify apenas reflete o que o Odoo diz. Nunca ajuste stock diretamente no Shopify quando a integração está ativa.
Pedidos duplicados
Problema: Um webhook pode chegar duas vezes (o Shopify garante entrega at-least-once). Se não gerir idempotência, cria o pedido duas vezes no Odoo.
Solução: Guarde o order_id do Shopify no Odoo (no campo client_order_ref ou num campo personalizado) e verifique antes de criar:
def is_order_already_imported(models, uid, shopify_order_id):
"""Verifica se um pedido do Shopify já foi importado para o 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 produto dessincronizados
Problema: O Shopify usa variant_id e product_id. O Odoo usa product.product (variante) e product.template (produto base). O mapeamento não é trivial, especialmente para produtos com múltiplas variantes (tamanho, cor).
Solução: Mantenha uma tabela de mapeamento explícita. Pode ser tão simples quanto um campo personalizado em product.product no Odoo que armazena o shopify_variant_id:
# Adicionar campo shopify_variant_id ao modelo product.product no Odoo
# Num módulo personalizado (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,
)
Falhas silenciosas em webhooks
Problema: Um webhook do Shopify falha (timeout, erro 500) e o pedido nunca chega ao Odoo. O Shopify tenta novamente durante 48 horas mas se o problema persistir, é descartado.
Solução: Implemente um processo de compensação: um cron job que a cada hora consulta os pedidos do Shopify das últimas 2 horas e verifica que todos existem no Odoo. Se faltar algum, importa-o.
// Processo de reconciliação: cron a cada hora
async function reconcileRecentOrders() {
const oneHourAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
// Obter pedidos recentes do 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} não encontrado no Odoo. A reimportar...`);
await importOrderToOdoo(order);
}
}
}
Arquitetura recomendada para lojas com mais de 1000 pedidos/dia
Com volume elevado, a arquitetura deve ser resiliente a picos de tráfego, falhas parciais e atrasos de rede:
[Shopify Webhooks]
|
v
[Fila de mensagens: Redis / RabbitMQ]
|
v
[Workers de processamento (múltiplas instâncias)]
|
+---> [Odoo XML-RPC / REST API]
|
+---> [Base de dados de auditoria]
|
v
[Cron de reconciliação (a cada 1h)]
|
v
[Alertas: Slack / PagerDuty em caso de discrepâncias]
Princípios fundamentais:
- Fila de mensagens: Os webhooks do Shopify escrevem numa fila (Redis Streams, RabbitMQ). Os workers processam de forma assíncrona. Isto evita que um pico de pedidos sobrecarregue o Odoo.
- Workers escaláveis: Múltiplos processos worker que consomem a fila em paralelo. Escalam horizontalmente conforme a carga.
- Idempotência garantida: Cada operação tem uma
idempotency_keybaseada no ID do evento do Shopify. Se um worker processar o mesmo evento duas vezes, o resultado é o mesmo. - Auditoria completa: Cada operação (webhook recebido, pedido criado, stock atualizado) é registada numa base de dados com timestamp, payload e resultado. Fundamental para debugging.
- Circuit breaker: Se o Odoo não responder durante mais de N segundos, o worker para de tentar e coloca as mensagens numa fila de retenção. Isto evita cascatas de falhas.
Checklist de implementação
Antes de colocar a integração em produção, verifique cada ponto:
Preparação do catálogo
- Todos os produtos têm SKU único no Odoo (
default_code) - Os SKUs do Odoo coincidem com os SKUs/referências do Shopify
- Os produtos com variantes (tamanho, cor) estão corretamente mapeados variante a variante
- A tabela de mapeamento
shopify_variant_id ↔ odoo_product_idestá completa
Configuração técnica
- Webhooks do Shopify configurados para:
orders/create,orders/updated,orders/cancelled,refunds/create - Verificação de assinatura HMAC ativa em todos os endpoints de webhook
- Variáveis de ambiente configuradas (nunca credenciais no código)
- Logs estruturados ativados com nível INFO ou DEBUG na fase de testes
Fluxos de dados
- Novo pedido Shopify → pedido de venda no Odoo (testado com pedidos reais)
- Cancelamento no Shopify → cancelamento no Odoo
- Devolução no Shopify → devolução/nota de crédito no Odoo
- Alteração de stock no Odoo → atualização no Shopify (testado com ajuste de inventário manual)
- Novo cliente no Shopify → parceiro no Odoo (sem duplicados)
Resiliência
- Idempotência verificada (processar o mesmo webhook duas vezes não cria duplicados)
- Processo de reconciliação periódica ativo
- Alertas configurados para falhas de sincronização
- Plano de recuperação em caso de queda do Odoo (a fila de mensagens retém os dados sem perda)
Testes de carga
- Simulado pico de pedidos (usando Shopify development store + dados de teste)
- Verificado que o tempo de processamento de pedido é inferior a 30 segundos em condições normais
Como a Soamee pode ajudar
Na Soamee somos especialistas em integrações ERP e e-commerce. Implementámos integrações Odoo–Shopify para clientes com volumes muito diferentes, desde lojas em crescimento até operações com milhares de pedidos diários.
A nossa abordagem começa sempre por perceber o seu caso concreto: que dados precisa de sincronizar, com que frequência, que lógica de negócio particular tem (múltiplos armazéns, tarifas diferentes por canal, gestão de bundles, dropshipping parcial). Só então recomendamos o método adequado, seja um conector padrão, um middleware à medida ou uma arquitetura baseada em eventos.
Se está a considerar integrar Odoo com Shopify, ou se tem uma integração existente que não está a funcionar bem, agende uma consultoria gratuita. Em 45 minutos podemos dar-lhe um roteiro claro com opções, estimativa de esforço e recomendação técnica.
Conclusão
A integração entre Odoo e Shopify não é uma questão de “se” mas de “como”. O método certo depende do seu volume, da complexidade da sua lógica de negócio e dos seus recursos técnicos:
- Conectores do marketplace: Ideal para começar rapidamente com fluxos padrão e volume moderado.
- Make/Zapier: Para validar a necessidade sem investimento em desenvolvimento, com volume baixo.
- Integração à medida com API: A opção mais robusta e flexível para operações que escalam, com lógica própria e resiliência real.
Seja qual for o método que escolha, os pilares são os mesmos: SKU como chave de mapeamento, idempotência em todos os fluxos, fonte de verdade única para o stock e um processo de reconciliação que deteta e corrige discrepâncias antes de se tornarem problemas para o cliente.