Ir al contenido

Webhooks y APIs externas: Odoo como integrador

Endpoints REST, consumo de servicios terceros y gestión de errores idempotentes
20 de agosto de 2026 por
Webhooks y APIs externas: Odoo como integrador
ATEMI, Aitor Atencia

Odoo · Integraciones · Webhooks

Un webhook que falla en silencio es peor que una integración que no existe: da la ilusión de que los datos están sincronizados cuando en realidad llevan días desincronizados. Recibir eventos externos en Odoo exige pensar en reintentos e idempotencia desde la primera línea, no como un añadido posterior.

Logo Odoo
Un endpoint que responde 200 antes de terminar de procesar es la causa más común de eventos perdidos.

El endpoint

Un controlador HTTP para recibir el evento, no para procesarlo

Un webhook entrante en Odoo es un controlador con auth='public' (o con validación de firma propia) que responde rápido y delega el trabajo pesado. Procesar el payload de forma síncrona dentro del propio request es arriesgado: si el proveedor externo tiene un timeout corto, cualquier lentitud en tu ORM hace que reintente el mismo evento y lo duplique.

# controllers/webhook.py
from odoo import http
from odoo.http import request

class WebhookController(http.Controller):

    @http.route("/webhook/pedidos", type="json", auth="public", methods=["POST"], csrf=False)
    def recibir_pedido(self, **payload):
        if not self._firma_valida(request.httprequest):
            return {"error": "firma inválida"}
        request.env["mi_modulo.evento_webhook"].sudo().create({
            "payload": str(payload),
            "estado": "pendiente",
        })
        return {"ok": True}

Procesar después

Guarda el evento crudo y procésalo con un cron o queue_job

Separar "recibir" de "procesar" te da margen: si el procesado falla, el evento crudo sigue ahí para reintentarlo sin pedirle al proveedor externo que reenvíe nada. Un modelo simple con estado (pendiente / procesado / error) y un cron que lo recorra es suficiente para la mayoría de integraciones; para volumen alto, queue_job evita bloquear el worker principal.

# models/evento_webhook.py
class EventoWebhook(models.Model):
    _name = "mi_modulo.evento_webhook"

    payload = fields.Text()
    estado = fields.Selection([
        ("pendiente", "Pendiente"),
        ("procesado", "Procesado"),
        ("error", "Error"),
    ], default="pendiente")
    intentos = fields.Integer(default=0)

    def _procesar(self):
        for evento in self:
            try:
                evento._aplicar_al_pedido()
                evento.estado = "procesado"
            except Exception as exc:
                evento.intentos += 1
                evento.estado = "error" if evento.intentos >= 5 else "pendiente"

El detalle que rompe todo

Idempotencia: el mismo evento puede llegar dos veces

Casi ningún proveedor de webhooks garantiza entrega exactamente-una-vez; garantizan "al menos una vez". Si tu procesado no es idempotente (crear un pedido, sumar un stock) un reintento legítimo del proveedor duplica datos. Guardar el event_id externo con una constraint de unicidad es la defensa más simple y efectiva.

SituaciónQué hacer
Evento con ID único del proveedorConstraint de unicidad sobre ese ID antes de procesar
Procesado pesado (crear factura, sincronizar stock)Encolar y responder rápido al webhook
El proveedor no firma sus payloadsToken secreto en la URL o cabecera, nunca confiar a ciegas
Consumir una API externa desde Odoorequests con timeout explícito y reintentos acotados

Resumen

Un webhook fiable separa recepción de procesado, guarda el evento crudo antes de tocar el negocio, y trata cada evento como si pudiera llegar duplicado. La idempotencia no es un extra: es lo que diferencia una integración robusta de una que un día duplica pedidos sin que nadie lo note a tiempo.

en Odoo
Performance en Odoo: índices, read_group y batch processing
Evitar N+1, optimizar queries y escoger bien entre search_read y read