TL;DR: Configure /whapi/webhook como controlador público de Odoo con csrf=False, suscríbase a eventos messages.post de Whapi y guarde cada message.id antes de crear crm.lead. Envíe alertas de etapa con POST https://gate.whapi.cloud/messages/text y Authorization: Bearer. Pruebe el flujo entrante en Community Edition con el Sandbox gratuito antes de actualizar el canal.
Los desarrolladores de Odoo Community Edition implementan intake de leads CRM con webhooks entrantes de Whapi y REST saliente en un módulo Python propio. Entregable copy-paste: webhook entrante, crm.lead idempotente, alerta de etapa saliente.
Si los envíos salientes de WhatsApp funcionan pero las respuestas del cliente nunca llegan al CRM, el fallo casi siempre está en el enrutamiento del webhook entrante, no en su token API. En el foro de Odoo, los integradores reportan exactamente ese patrón: "Sending messages works, but incoming messages are not appearing in Odoo." El conector nativo de Meta para Odoo exige licencia Enterprise y de 2 a 7 días hábiles de verificación comercial antes de probar un pipeline real. Whapi se conecta con escaneo QR y publica JSON en su controlador en minutos.
Por qué Whapi REST habilita el intake de leads CRM en Community
Enterprise bloquea WhatsApp nativo; Whapi REST habilita intake CRM en Community sin verificación Meta.
La app oficial de WhatsApp de Odoo es solo Enterprise. Los equipos de Community Edition chocan con un muro: sin conector integrado, sin ruta soportada para crear crm.lead automáticamente desde el chat. Los módulos de Apps Store cubren el hueco a $50–$300+ con licencias OPL-1 y sin código fuente para depurar cuando el tráfico entrante se detiene.
Whapi.Cloud se conecta mediante sockets de sesión web, el mismo mecanismo que usa WhatsApp Web. Escanee un QR en el panel, copie su token Bearer y registre la URL del webhook. En la API oficial de WhatsApp Business, la verificación comercial suele tardar de 2 a 7 días hábiles antes del envío en producción; con Whapi, la mayoría de los equipos envían un mensaje entrante de prueba la misma tarde en que instalan el módulo.
Inmobiliarias, tiendas e-commerce y recepciones de clínicas que usan Odoo CRM aplican este patrón a diario: una consulta por WhatsApp crea un lead puntuado y un cambio de etapa dispara un recordatorio de seguimiento. Aquí omitimos la verificación HMAC de hub.challenge de Meta; esa ruta corresponde a conectores Enterprise de Meta, mientras el webhook JSON de Whapi ya cubre Community Edition.
Comparación de enfoques de integración
Elija la ruta Python que pueda desplegar en Community Edition y depurar cuando el tráfico entrante se detenga. Cuatro opciones dominan hoy los proyectos CRM WhatsApp en Odoo.
| Enfoque | Community Edition | Tiempo de setup | Propiedad del código | Notas de fiabilidad entrante |
|---|---|---|---|---|
| WhatsApp nativo Odoo Enterprise | No | Días (verificación Meta + WABA) | Módulo core cerrado | Reportes del foro: saliente OK, entrante silencioso hasta reset WABA/webhook |
| Apps Store OPL-1 (p. ej. whatsapp_lead_hook) | Sí (según módulo) | Horas (tokens Meta + instalación) | Código cerrado (~228+ LOC caja negra) | Aún depende de Meta WABA; troubleshooting limitado |
| Controlador Python Meta Cloud API personalizado | Sí | Días–semanas | Total (usted mantiene HMAC + refresh de token) | Fallos de número en producción reportados; tokens expiran ~60 días |
| Whapi REST + Odoo http.Controller (esta guía) | Sí | ~2 min QR + instalación del módulo | Módulo Python completo bajo su control | Webhook JSON; sin gate de plantillas en notificaciones de etapa |
El módulo OCA mail_gateway_whatsapp es AGPL y compatible con Community, pero aún requiere Meta WABA, cableado manual del webhook en el panel de Facebook y credenciales de prueba que rotan cada 24 horas. Enruta correo de Discuss; no incluye un pipeline idempotente de crm.lead listo para usar. Un módulo Odoo con ruta pública supera insertar n8n en el medio cuando necesita un único stack trace del controlador para bugs de leads duplicados.
Estructura de archivos del módulo
Arme un addon whapi_crm_lead con __manifest__.py, controllers/whatsapp_webhook.py, models/crm_lead.py y security/ir.model.access.csv. Instálelo en Odoo 17/18 Community con -i whapi_crm_lead. Use Whapi Sandbox (5 conversaciones activas/mes, 150 mensajes/día, gratis permanente) para validar el parsing entrante antes de que el tráfico de producción golpee el mismo controlador.
Mapeo de eventos webhook Whapi a campos Odoo
Parse el payload Whapi, empareje teléfono del partner, actualice crm.lead abierto, publique en chatter. Esa cadena de cuatro pasos es la mitad entrante del pipeline.
Whapi publica un cuerpo JSON WebhookPayload en su URL en el evento messages.post. El array messages contiene objetos Message de la referencia de formato de webhooks entrantes. Ignore filas donde from_me es true; son ecos salientes, no leads de clientes.
| Campo webhook Whapi | Destino Odoo | Notas |
|---|---|---|
messages[].id |
crm.lead.x_whapi_message_id (Char, indexado) |
Clave de idempotencia; verificar antes de create |
messages[].from o chat_id |
res.partner.phone / mobile |
Normalizar a E.164; fallback últimos 10 dígitos |
messages[].text.body |
crm.lead.description + cuerpo mail.message |
Solo cuando type es text |
messages[].from_name |
crm.lead.contact_name |
Pushname cuando esté presente |
messages[].timestamp |
contexto crm.lead.create_date |
Epoch Unix del proveedor |
Configure el webhook del canal con PATCH https://gate.whapi.cloud/settings, pasando una entrada webhooks con su URL HTTPS, mode: POST y eventos que incluyan messages. Agregue un secreto compartido en headers y valídelo en el controlador antes de parsear JSON.
Construir el controlador webhook
Devuelva HTTP 200 en menos de un segundo, aunque el procesamiento del lead continúe después. Whapi reintenta callbacks fallidos con backoff lineal cuando su endpoint no responde 200.
Registre una ruta pública porque los servidores de Whapi no tienen cookie de sesión Odoo. Desactive CSRF solo en esa ruta. Valide su header compartido antes de tocar request.jsonrequest. El modelo evento-reacción aplica aquí: Whapi hace POST de un evento, su controlador reacciona, y un handler lento dispara reintentos.
# 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"}
Apunte Whapi a https://your-odoo-domain.com/whapi/webhook. Ejecute POST https://gate.whapi.cloud/settings/webhook_test con la misma URL para confirmar alcance antes de enviar tráfico real de clientes.
Whapi requiere una URL HTTPS pública. Equipos en LAN privada necesitan un reverse proxy con terminación TLS antes de que pase la prueba del webhook.
Creación idempotente de leads
Guarde message_id antes de create; los reintentos del webhook duplican leads CRM en silencio. Aplique la compuerta de idempotencia en cada evento entrante.
Whapi puede entregar el mismo payload messages.post más de una vez si su servidor es lento o devuelve un status distinto de 200. Odoo no tiene upsert nativo para IDs externos de webhook. Un desarrollador que documentó un stack Odoo 19 + n8n en dev.to lo resumió: aparecen leads duplicados cuando crea un registro en cada POST sin verificar un identificador único de mensaje.
# 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"),
})
Fusione mensajes de seguimiento en la oportunidad abierta mientras stage_id.is_won sea false. Cree un lead nuevo solo después de que el trato anterior esté ganado o perdido.
Notificaciones salientes de etapa vía Whapi REST
El cambio de etapa CRM dispara mensaje Whapi REST, cerrando el ciclo entrante-saliente. Enganche escrituras de stage_id, no solo eventos create.
Cuando un vendedor mueve un lead a "Visita programada", el cliente debe recibir confirmación por WhatsApp sin copiar-pegar manual. Sobrescriba write() en crm.lead, detecte cambios de stage_id y llame al endpoint de texto de Whapi. En la API oficial de WhatsApp Business, mensajes salientes fuera de la ventana de 24 horas de servicio al cliente requieren plantillas preaprobadas; Whapi envía texto estilo sesión por sockets web sin envío de plantillas, ideal para alertas de etapa ligadas a cambios de estado 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 el endpoint send text message: POST /messages/text requiere to (teléfono o Chat ID) y body. Guarde el token API en ir.config_parameter, nunca en git.
La suscripción plana de Whapi mantiene costos predecibles de notificaciones de etapa frente a facturación por plantilla de BSP en rutas oficiales.
Solución de fallos entrantes
Sincronización WhatsApp unidireccional significa que fallaron los webhooks entrantes, no la configuración saliente. Inicie cada sesión de depuración confirmando que Whapi entregó messages.post a su URL.
Los síntomas en hilos del foro Odoo se repiten en versiones: las plantillas envían, las respuestas desaparecen. Hemos visto el mismo patrón unidireccional en instalaciones Whapi cuando la URL del webhook aún apunta a un túnel staging obsoleto tras un corte DNS. Un usuario escribió tras pasar verificaciones Meta: "This would mean the so-publicised whatsapp funcionality in Odoo is absolutely useless." En Whapi, el fallo equivalente suele ser local: URL incorrecta, HTTPS faltante, secreto incorrecto u Odoo devolviendo 403 en ruta pública bloqueada por filtro multi-base de datos.
-
Prueba webhook: Ejecute
POST /settings/webhook_testdesde el panel Whapi. Espere HTTP 200 y un hit en logs Odoo en 2 segundos. -
¿Está
messageshabilitado en la entrada webhook? Los pings de salud de conexión solos no crearán leads. -
CSRF / auth: Solo la ruta Whapi debe usar
auth="public", csrf=False. Envolver rutas JSON con auth portal por error devuelve páginas HTML de login a Whapi. -
¿Filas duplicadas? Busque
x_whapi_message_iden ambos registros. Valores vacíos indican que la compuerta de idempotencia nunca corrió.
Si encuentra comportamiento inesperado tras estas verificaciones, contacte al equipo de soporte de Whapi.Cloud por el chat en whapi.cloud. El equipo ayuda activamente a resolver problemas de webhooks en producción.
Trampas multi-base de datos en hosts staging
Los controladores auth='public' fallan en hosts multi-DB a menos que el addon esté cargado en server_wide_modules y el filtro de base de datos enrute tráfico Whapi a la base CRM correcta. Síntoma: la prueba webhook devuelve HTTP 200 en la base vacía incorrecta mientras el CRM de producción permanece silencioso. Configure un dbfilter explícito en el hostname público que registre en Whapi.
Conecte webhooks entrantes Whapi y REST saliente en Community Edition con este módulo de cuatro archivos, y desarrolladores Odoo/Python pueden probar el pipeline CRM completo el mismo día que escanean el QR.









