n8n convient à ce qui se passe autour d'Odoo : récupérer un formulaire externe, prévenir un chat, monter un rapport quotidien. Ce qui change l'état de l'activité à l'intérieur (prix, réservations de stock, validations de bons de livraison, rapprochement de paiements) demande un module. La frontière se voit en exécutant les deux contre un vrai Odoo : cet article est tiré d'un laboratoire propre avec Odoo 19.0-20260630 et n8n 2.30.6.
Idée clé : un flux qui appelle l'API d'Odoo n'exécute pas son interface. Il peut recevoir un 200 avec un assistant à l'intérieur et croire avoir validé un bon de livraison qui reste ouvert.
Comment n8n parle à Odoo : quatre chemins, pas un seul
- XML-RPC :
/xmlrpc/2/commonpour s'authentifier et/xmlrpc/2/objectpourexecute_kw. Celui de la documentation. - JSON-RPC : un
POST /jsonrpcavecservice,methodetargs. Le même service en JSON. - Session web :
/web/session/authenticateet/web/dataset/call_kwavec le cookie. Ce que fait votre navigateur. - L'API JSON-2, uniquement dans Odoo 19 :
POST /json/2/<modelo>/<método>avecAuthorization: bearer <clave API>etX-Odoo-Database. Sans session : les arguments sont des champs nommés,contextcompris.
Les trois premiers continuent de fonctionner en 19, mais le serveur avertit dans le log : « The /xmlrpc, /xmlrpc/2 and /jsonrpc endpoints are deprecated in Odoo 19 and scheduled for removal in Odoo 22 ». Un nouveau flux contre un 19 devrait passer par /json/2 ; s'il sert aussi 17 et 18, par la session.
# Sesion + call_kw: funciona igual en 17, 18 y 19.
POST /web/session/authenticate
{"params":{"db":"lab","login":"n8n_bot2","password":"..."}}
-> 200 {"result":{"uid":6,"server_version":"19.0-20260630"}} # + cookie
POST /web/dataset/call_kw # con esa cookie
{"params":{"model":"crm.lead","method":"create",
"args":[{"name":"Lead web","type":"lead"}],
"kwargs":{"context":{"lang":"es_ES"}}}}
-> 200 {"result": 10}
# Clave API en la cabecera, sin sesion: solo Odoo 19.
POST /json/2/crm.lead/create
Authorization: bearer <clave> X-Odoo-Database: lab
{"vals_list":[{"name":"Lead via /json/2 (API key)"}]}
-> 200 [9]
# Sin cabecera -> 401 "User not authenticated, use an API Key with a
# Bearer Authorization header."
# Metodo privado -> 403 "Private methods (such as
# 'crm.lead._compute_display_name') cannot be called remotely." Le nœud Odoo de n8n 2.30.6 choisit pour vous selon l'identifiant : celui à clé API passe par /json/2 ; le classique passe par /jsonrpc avec execute, positionnel, qui n'accepte pas kwargs et ne peut pas transmettre de contexte. L'identifiant à clé API prévient en outre qu'il requiert « Odoo 19+ and a Custom pricing plan ».
Le nœud HTTP Request, tel qu'il est resté
Remarquez où la clé n'est pas : le corps ne contient que des données et l'en-tête Authorization est posé par un identifiant n8n, chiffré et renouvelé à part.
// Lineas del nodo real, copiadas de `n8n export:workflow`.
"type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2,
"url": "http://odoo:8069/json/2/crm.lead/create",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": { "parameters": [
{ "name": "X-Odoo-Database", "value": "lab" } ] },
"jsonBody": "={{ JSON.stringify({ vals_list: [{ name: 'Web: ' + $json.body.empresa, contact_name: $json.body.nombre, email_from: $json.body.email, description: $json.body.mensaje, type: 'lead' }] }) }}",
"credentials": { "httpHeaderAuth": { "name": "Odoo lab - API key (header)" } }
// La clave no esta aqui: la pone esa credencial.Le flux n'entre pas non plus en administrateur, mais en utilisateur technique avec juste les bons groupes et le plafond qu'Odoo 19 impose à sa clé :
# Usuario tecnico nuevo (solo grupo de usuario interno):
POST /json/2/crm.lead/create
-> 403 "No puede crear informes 'Lead' (crm.lead) ...
- Sales/Administrator
- Sales/User: Own Documents Only"
# Tras anadirle sales_team.group_sale_salesman: -> 200
# Su clave API tampoco dura lo que uno quiera:
_generate(..., +200 dias) -> "No puede exceder 90.0 dias."
_generate(..., sin fecha) -> "La clave API debe tener una
fecha de vencimiento"Ce plafond de 90 jours est la date à laquelle votre flux cessera de fonctionner sans que personne n'y touche. Notez-la le jour où vous créez la clé.
Cinq automatisations qui tiennent dans un flux
Elles ont toutes le même point commun : le fait a déjà eu lieu et le flux ne fait que le déplacer.
| Cas | Déclencheur | Appel à Odoo |
|---|---|---|
| Lead depuis un formulaire externe | Webhook n8n | crm.lead / create |
| Alerte au chat à la validation d'un bon de livraison | Automatisation Odoo avec action de webhook | Aucun : Odoo pousse |
| Contacts vers un outil de marketing | Planifié, chaque nuit | res.partner / search_read par write_date |
| Alerte de stock bas | Planifié, chaque matin | product.product / search_read de qty_available |
| Rapport quotidien à la direction | Planifié | read_group sur sale.order |
Le deuxième cas a ses petites lignes. L'action de webhook envoie _model, _id et le nom de l'action, et c'est du « lancer et oublier » : un POST d'une seconde, sans nouvelle tentative. En 19 il part après le commit ; en 17 et 18 il est dans la transaction, donc un n8n lent fait attendre l'utilisateur.
# Accion de servidor state='webhook' al crear un lead.
INFO Webhook call to http://n8n:5678/webhook/odoo-lead
INFO Webhook call to http://n8n:5678/webhook/odoo-lead - succeeded
# Apuntando a un flujo borrado (el lead se crea igual):
WARNING Webhook call failed: 404 Client Error: Not Found for url:
http://n8n:5678/webhook/flujo-borrado
WARNING Webhook call timed out after 1s - it may or may not have failed.Cinq qui non, et la raison exacte
- Calculer des prix ou des remises. Odoo les calcule à partir du tarif ; si le flux les calcule à l'extérieur, quand le tarif change il y a deux vérités.
- Réserver du stock pour plusieurs canaux. Odoo résout la course dans une transaction ; un flux qui lit, décide et écrit séparément vend en trop.
- Rapprocher des paiements. Apparier et comptabiliser vont ensemble : si l'un se fait dehors et l'autre dedans, une coupure réseau laisse un demi-rapprochement.
- Ce que quelqu'un va auditer. L'historique de n8n n'est pas le chatter : « qui a changé ça et pourquoi ? » se répond dans le document.
- Ce qui dépend d'un champ qui change entre versions. Nous l'avons vérifié en écrivant dans Odoo 19 ce qui fonctionnait en 18.
Le cas qui résume le mieux : valider un bon de livraison depuis un flux. Nous en avons préparé un de 10 unités avec 4 réservées et appelé button_validate, comme le fait le bouton.
# WH/OUT/00003: 10 pedidas, 4 reservadas.
POST /web/dataset/call_kw
{"model":"stock.picking","method":"button_validate","args":[[2]]}
-> 200 {"result": {"name": "¿Crear entrega parcial?",
"type": "ir.actions.act_window",
"res_model": "stock.backorder.confirmation", ...}}
read -> [{"name":"WH/OUT/00003","state":"assigned"}] # sigue abiertoHTTP 200, sans erreur, et un bon de livraison en assigned. Pour n8n l'étape est passée au vert ; pour l'entrepôt, la commande n'est pas partie. Un module reconnaît l'assistant et le consigne dans le document : la même frontière que dans Odoo 19 face aux connecteurs de middleware.
Et les champs qui changent de nom entre versions, ce qui casse un flux stable après une migration :
# Odoo 19, escribiendo lo que funcionaba en 17 y 18:
res.users write {"groups_id": [[4, 1]]}
-> "Invalid field 'groups_id' in 'res.users'" # en 19 se llama group_ids
stock.move create {"name": "Latiguillo", "product_id": 1, ...}
-> "Invalid field 'name' in 'stock.move'" # en 17 y 18 era obligatorioNous maintenons le même code sur Odoo 19, 18 et 17 dans 105 modules publiés : ces différences sont à la charge du module, pas du client. C'est aussi la limite d'Odoo Studio quand le processus cesse d'être un champ et devient une règle.
Les quatre erreurs que nous voyons toujours
- La boucle. Une automatisation prévient n8n lors du changement d'un champ, le flux écrit dans Odoo et ce qu'il écrit la redéclenche. On la coupe avec une condition dans le déclencheur.
- L'identifiant en clair. Ni dans le corps du nœud ni dans un nœud de code : dans un identifiant n8n, avec un utilisateur technique aux groupes minimaux.
- Sans idempotence. Un webhook relancé de l'autre côté crée à nouveau l'enregistrement : trois
POSTidentiques vers notre flux ont donné trois leads. - Sans vraie nouvelle tentative. Un nœud ne réessaie que si vous activez Retry On Fail, et le moteur limite les essais à cinq et l'attente à cinq secondes. Suffisant pour un chat ; pas pour un transporteur : notre module Correos Express réessaie à 5, 15, 30, 60, 120 et 240 minutes.
La solution n'est pas dans n8n, elle est dans la conception : conservez dans Odoo la référence externe et vérifiez-la avant de créer. Dans un module c'est une contrainte unique en base de données ; dans un flux, une lecture préalable avec fenêtre de course.
Quand un flux devient un module
Le flux est une façon peu coûteuse de découvrir si le processus mérite d'exister. Le signe qu'il est devenu trop juste est l'un de ces six :
- Il écrit un champ qu'Odoo calcule lui-même.
- Deux écritures doivent avoir lieu ensemble ou pas du tout.
- Quelqu'un auditera dans Odoo ce qui s'est passé et quand.
- Le processus doit survivre à la prochaine migration.
- Il faut pouvoir répéter l'appel sans rien dupliquer.
- L'utilisateur a besoin de réessayer depuis le document lui-même.
À ce stade on ne jette pas le flux : on porte dans le module la partie qui touche l'état de l'activité et n8n garde la périphérie. C'est pareil pour automatiser les tâches administratives et pour l'intégration avec Signaturit.
Résumé : si le flux tombe et que la seule conséquence est que quelqu'un ne reçoit pas une alerte, n8n suffit. S'il tombe et que l'entrepôt ou le livre de factures se retrouvent dans un état que personne ne sait lire, c'était un module.
Questions fréquentes
Puis-je passer la langue ou la société dans l'appel ?
Par les trois chemins modernes oui : kwargs.context dans call_kw et dans execute_kw, et un champ context dans /json/2 (un pays sort « Spain » avec en_US et « España » avec es_ES). Pas avec l'identifiant classique, qui appelle execute, positionnel.
Où conserver la clé API et combien de temps dure-t-elle ?
Dans un identifiant n8n, jamais dans le corps du nœud. Dans Odoo 19 la clé d'un utilisateur non administrateur nécessite une expiration, et le plafond est fixé par le groupe : 90 jours pour un utilisateur interne.
Mon flux casse-t-il en migrant d'Odoo 18 à 19 ?
Cela dépend des champs touchés, et il n'y a pas d'avertissement : res.users.groups_id est passé à group_ids et stock.move.name, obligatoire en 17 et 18, n'existe pas en 19. Testez vos flux contre une copie.
Liens utiles dans FlexigoTech
Ce que nous faisons à ce sujet
Vous avez des flux n8n contre Odoo et vous ne savez plus lesquels tiennent ?
Nous les examinons, nous vous disons lesquels restent tels quels et lesquels il faut porter dans un module, et nous réalisons le module si besoin. Écrivez à comercial@flexigobe.com ou appelez le +34 616 809 504.

