TL;DR: Configure /whapi/webhook como controller público do Odoo com csrf=False, assine eventos messages.post do Whapi e armazene cada message.id antes de criar crm.lead. Envie alertas de estágio com POST https://gate.whapi.cloud/messages/text e Authorization: Bearer. Teste o fluxo inbound no Community Edition com o Sandbox gratuito antes de fazer upgrade do canal.
Desenvolvedores do Odoo Community Edition entregam intake de leads CRM com webhooks inbound do Whapi e REST outbound em um módulo Python próprio. Entregável copy-paste: webhook inbound, crm.lead idempotente, alerta de estágio outbound.
Se envios outbound pelo WhatsApp funcionam, mas as respostas do cliente nunca chegam ao CRM, a falha quase sempre está no roteamento do webhook inbound — não no token da API. No fórum do Odoo, integradores relatam exatamente esse padrão: "Sending messages works, but incoming messages are not appearing in Odoo." O conector nativo da Meta para Odoo exige licença Enterprise e de 2 a 7 dias úteis de verificação comercial antes de testar um pipeline real. O Whapi conecta por QR code e publica JSON no seu controller em minutos.
Por que o Whapi REST libera intake de leads CRM no Community
Enterprise bloqueia WhatsApp nativo; Whapi REST libera intake CRM no Community sem verificação Meta.
O app oficial de WhatsApp do Odoo é somente Enterprise. Equipes no Community Edition batem num muro: sem conector integrado, sem caminho suportado para criar crm.lead automaticamente a partir do chat. Módulos da Apps Store preenchem a lacuna por $50–$300+ com licenças OPL-1 e sem código-fonte para debugar quando o tráfego inbound trava.
O Whapi.Cloud conecta via sockets de sessão web — o mesmo mecanismo do WhatsApp Web. Escaneie um QR code no painel, copie seu token Bearer e registre a URL do webhook. Na API oficial do WhatsApp Business, a verificação comercial costuma levar de 2 a 7 dias úteis antes do envio em produção; com o Whapi, a maioria das equipes envia uma mensagem inbound de teste na mesma tarde em que instala o módulo.
Imobiliárias, lojas de e-commerce e recepções de clínicas que rodam Odoo CRM usam esse padrão todo dia: uma consulta pelo WhatsApp cria um lead pontuado, e uma mudança de estágio dispara um lembrete de follow-up. Aqui omitimos a verificação HMAC de hub.challenge da Meta; esse caminho pertence a conectores Enterprise da Meta, enquanto o webhook JSON do Whapi já cobre o Community Edition.
Comparação de abordagens de integração
Escolha o caminho Python que você consegue implantar no Community Edition e debugar quando o tráfego inbound travar. Quatro opções dominam projetos CRM WhatsApp no Odoo hoje.
| Abordagem | Community Edition | Tempo de setup | Propriedade do código | Notas de confiabilidade inbound |
|---|---|---|---|---|
| WhatsApp nativo Odoo Enterprise | Não | Dias (verificação Meta + WABA) | Módulo core fechado | Relatos no fórum: outbound OK, inbound silencioso até reset WABA/webhook |
| Apps Store OPL-1 (ex.: whatsapp_lead_hook) | Sim (depende do módulo) | Horas (tokens Meta + instalação) | Código fechado (~228+ LOC caixa preta) | Ainda depende de Meta WABA; troubleshooting limitado |
| Controller Python Meta Cloud API customizado | Sim | Dias–semanas | Total (você mantém HMAC + refresh de token) | Falhas de número em produção reportadas; tokens expiram ~60 dias |
| Whapi REST + Odoo http.Controller (este guia) | Sim | ~2 min QR + instalação do módulo | Módulo Python completo sob seu controle | Webhook JSON; sem gate de template em notificações de estágio |
O módulo OCA mail_gateway_whatsapp é AGPL e compatível com Community, mas ainda exige Meta WABA, configuração manual de webhook no painel do Facebook e credenciais de teste que rotacionam a cada 24 horas. Ele roteia e-mail do Discuss; não entrega um pipeline idempotente de crm.lead pronto para uso. Um módulo Odoo com rota pública supera inserir n8n no meio quando você precisa de um único stack trace do controller para bugs de leads duplicados.
Estrutura de arquivos do módulo
Monte um addon whapi_crm_lead com __manifest__.py, controllers/whatsapp_webhook.py, models/crm_lead.py e security/ir.model.access.csv. Instale no Odoo 17/18 Community com -i whapi_crm_lead. Use o Whapi Sandbox (5 conversas ativas/mês, 150 mensagens/dia, gratuito permanente) para validar o parsing inbound antes do tráfego de produção bater no mesmo controller.
Mapeamento de eventos webhook Whapi para campos Odoo
Faça parse do payload Whapi, encontre o telefone do partner, faça upsert do crm.lead aberto, publique no chatter. Essa cadeia de quatro passos é a metade inbound do pipeline.
O Whapi publica um corpo JSON WebhookPayload na sua URL no evento messages.post. O array messages contém objetos Message da referência de formato de webhooks inbound. Ignore linhas em que from_me é true; são ecos outbound, não leads de clientes.
| Campo webhook Whapi | Destino Odoo | Notas |
|---|---|---|
messages[].id |
crm.lead.x_whapi_message_id (Char, indexado) |
Chave de idempotência; verifique antes do create |
messages[].from ou chat_id |
res.partner.phone / mobile |
Normalize para E.164; fallback últimos 10 dígitos |
messages[].text.body |
crm.lead.description + corpo mail.message |
Somente quando type é text |
messages[].from_name |
crm.lead.contact_name |
Pushname quando presente |
messages[].timestamp |
contexto crm.lead.create_date |
Epoch Unix do provedor |
Configure o webhook do canal com PATCH https://gate.whapi.cloud/settings, passando uma entrada webhooks com sua URL HTTPS, mode: POST e eventos incluindo messages. Adicione um segredo compartilhado em headers e valide-o no controller antes de fazer parse do JSON.
Construir o controller webhook
Retorne HTTP 200 em até um segundo, mesmo que o processamento do lead continue depois. O Whapi repete callbacks com falha com backoff linear quando seu endpoint não responde 200.
Registre uma rota pública porque os servidores do Whapi não têm cookie de sessão Odoo. Desative CSRF somente nessa rota. Valide seu header compartilhado antes de tocar em request.jsonrequest. O modelo evento-reação se aplica aqui: o Whapi faz POST de um evento, seu controller reage, e um handler lento dispara retries.
# controllers/whatsapp_webhook.py
import logging
from odoo import http
from odoo.http import request
_logger = logging.getLogger(__name__)
class WhapiWebhookController(http.Controller):
@http.route("/whapi/webhook", type="json", auth="public", csrf=False, methods=["POST"])
def whapi_inbound(self, **kwargs):
# Without the shared-secret check, anyone who discovers this URL can POST fake leads.
expected = request.env["ir.config_parameter"].sudo().get_param("whapi.webhook_secret")
incoming = request.httprequest.headers.get("X-Whapi-Secret")
if not expected or incoming != expected:
return {"status": "forbidden"}
payload = request.jsonrequest or {}
messages = payload.get("messages") or []
Lead = request.env["crm.lead"].sudo()
for msg in messages:
if msg.get("from_me"):
continue
Lead.process_whapi_inbound(msg)
return {"status": "ok"}
Aponte o Whapi para https://your-odoo-domain.com/whapi/webhook. Execute POST https://gate.whapi.cloud/settings/webhook_test com a mesma URL para confirmar alcance antes de enviar tráfego real de clientes.
O Whapi exige URL HTTPS pública. Equipes em LAN privada precisam de reverse proxy com terminação TLS antes da prova do webhook passar.
Criação idempotente de leads
Armazene message_id antes do create; retries do webhook duplicam leads CRM em silêncio. Aplique o gate de idempotência em todo evento inbound.
O Whapi pode entregar o mesmo payload messages.post mais de uma vez se seu servidor estiver lento ou retornar status diferente de 200. O Odoo não tem upsert nativo para IDs externos de webhook. Um desenvolvedor que documentou stack Odoo 19 + n8n no dev.to resumiu: leads duplicados aparecem quando você cria um registro a cada POST sem checar um identificador único de mensagem.
# models/crm_lead.py
from odoo import api, fields, models
class CrmLead(models.Model):
_inherit = "crm.lead"
x_whapi_message_id = fields.Char(index=True, copy=False)
x_whapi_chat_id = fields.Char(copy=False)
@api.model
def process_whapi_inbound(self, msg):
message_id = msg.get("id")
if not message_id:
return
existing = self.search([("x_whapi_message_id", "=", message_id)], limit=1)
if existing:
return existing # idempotency gate: retry ignored
phone = msg.get("from") or msg.get("chat_id") or ""
body = (msg.get("text") or {}).get("body") or ""
partner = self.env["res.partner"]._find_or_create_from_whapi(phone, msg.get("from_name"))
open_lead = self.search([
("partner_id", "=", partner.id),
("type", "=", "opportunity"),
("active", "=", True),
("stage_id.is_won", "=", False),
], limit=1)
if open_lead:
open_lead.message_post(body=f"WhatsApp: {body}")
return open_lead
return self.create({
"name": f"WhatsApp - {partner.name or phone}",
"partner_id": partner.id,
"phone": partner.phone or phone,
"description": body,
"x_whapi_message_id": message_id,
"x_whapi_chat_id": msg.get("chat_id"),
})
Consolide mensagens de follow-up na oportunidade aberta enquanto stage_id.is_won for false. Crie um lead novo somente depois que o negócio anterior estiver ganho ou perdido.
Notificações outbound de estágio via Whapi REST
Mudança de estágio no CRM dispara mensagem Whapi REST, fechando o ciclo inbound-outbound. Engate escritas de stage_id, não só eventos create.
Quando um vendedor move um lead para "Visita agendada", o cliente deve receber confirmação pelo WhatsApp sem copiar e colar manualmente. Sobrescreva write() em crm.lead, detecte mudanças de stage_id e chame o endpoint de texto do Whapi. Na API oficial do WhatsApp Business, mensagens outbound fora da janela de 24 horas de atendimento exigem templates pré-aprovados; o Whapi envia texto estilo sessão via sockets web sem submissão de template — ideal para alertas de estágio ligados a mudanças de estado no CRM.
# models/crm_lead.py (continued)
import os
import requests
from odoo import models
WHAPI_BASE = "https://gate.whapi.cloud"
class CrmLead(models.Model):
_inherit = "crm.lead"
def write(self, vals):
prev_stage = {lead.id: lead.stage_id for lead in self}
res = super().write(vals)
if "stage_id" not in vals:
return res
token = self.env["ir.config_parameter"].sudo().get_param("whapi.api_token")
for lead in self:
if prev_stage.get(lead.id) == lead.stage_id:
continue
to = lead.x_whapi_chat_id or lead.phone
if not (to and token):
continue
text = f"Hi {lead.contact_name or 'there'}, your deal moved to: {lead.stage_id.name}."
# Missing Bearer token returns 401; log it, do not crash the CRM write().
resp = requests.post(
f"{WHAPI_BASE}/messages/text",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
json={"to": to, "body": text},
timeout=10,
)
if resp.status_code >= 400:
lead.message_post(body=f"Whapi stage alert failed: HTTP {resp.status_code}")
return res
Verificado contra o endpoint send text message: POST /messages/text exige to (telefone ou Chat ID) e body. Armazene o token da API em ir.config_parameter, nunca no git.
A assinatura fixa do Whapi mantém custos previsíveis de notificações de estágio em comparação com cobrança por template de BSP nos caminhos oficiais.
Solução de falhas inbound
Sincronização WhatsApp unidirecional significa que webhooks inbound falharam — não a configuração outbound. Inicie toda sessão de debug confirmando que o Whapi entregou messages.post na sua URL.
Sintomas em threads do fórum Odoo se repetem entre versões: templates enviam, respostas somem. Vimos o mesmo padrão unidirecional em instalações Whapi quando a URL do webhook ainda aponta para túnel staging obsoleto após corte de DNS. Um usuário escreveu depois de passar verificações Meta: "This would mean the so-publicised whatsapp funcionality in Odoo is absolutely useless." No Whapi, a falha equivalente costuma ser local: URL errada, HTTPS ausente, segredo incorreto ou Odoo retornando 403 em rota pública bloqueada por filtro multi-banco de dados.
-
Teste de webhook: Execute
POST /settings/webhook_testno painel Whapi. Espere HTTP 200 e um hit nos logs do Odoo em 2 segundos. -
messagesestá habilitado na entrada do webhook? Pings de saúde de conexão sozinhos não criam leads. -
CSRF / auth: Somente a rota Whapi deve usar
auth="public", csrf=False. Envolver rotas JSON com auth portal por engano devolve páginas HTML de login ao Whapi. -
Linhas duplicadas? Busque
x_whapi_message_idnos dois registros. Valores vazios indicam que o gate de idempotência nunca rodou.
Se encontrar comportamento inesperado após essas verificações, fale com a equipe de suporte do Whapi.Cloud pelo chat em whapi.cloud. A equipe ajuda ativamente a resolver problemas de webhooks em produção.
Armadilhas multi-banco de dados em hosts staging
Controllers auth='public' quebram em hosts multi-DB a menos que o addon esteja carregado em server_wide_modules e o filtro de banco roteie tráfego Whapi para o banco CRM correto. Sintoma: teste de webhook retorna HTTP 200 no banco vazio errado enquanto o CRM de produção fica silencioso. Defina um dbfilter explícito no hostname público que registrar no Whapi.
Conecte webhooks inbound Whapi e REST outbound no Community Edition com este módulo de quatro arquivos — e desenvolvedores Odoo/Python podem testar o pipeline CRM completo no mesmo dia em que escaneiam o QR code.









