Cet article est actuellement disponible en anglais uniquement. Traduction à venir.
By the end of this recipe, you will have a custom Zapier app that exposes your Odoo 19 instance to the 5,000+ apps in the Zapier marketplace, with triggers (new SO, new invoice, new contact), actions (create contact, create invoice, validate SO), and authentication via Odoo's standard XML-RPC + API key. Skill required: developer comfortable with Node.js and JSON Schema. Time required: 4 hours for the basic 3-trigger / 3-action app, 8 hours for a polished private app. ECOSIRE has built Zapier apps for several clients who needed Odoo to integrate with niche tools we did not want to write per-app connectors for, and the recipe below is the playbook.
The strategic reason this matters: Zapier acts as a bridge for the long tail of integrations. Big integrations (Shopify, Stripe, QuickBooks) deserve native connectors. Small integrations (a niche industry tool, a regional CRM, a specific accounting system) are not worth the per-app engineering — Zapier handles them at $20/month per Zap. One Zapier app covers all of them.
What you will need
- Zapier developer account: free at
developer.zapier.com. - Zapier CLI: install with
npm install -g zapier-platform-cli. - Odoo with API access: the standard XML-RPC endpoint (
/xmlrpc/2/object) is enabled by default on every Odoo instance. - Odoo API user: a dedicated user with restricted permissions (read sale.order, write res.partner, etc.) so the Zapier app cannot do more than necessary.
- Time: 4 hours basic, 8 hours polished.
- Node.js 18+ for the Zapier CLI.
Step-by-step
1. Initialize the Zapier app
mkdir zapier-odoo && cd zapier-odoo
zapier init . --template minimal
npm install
zapier register "Odoo by ECOSIRE"
This creates the app skeleton and registers it in your Zapier developer account. The directory now has index.js, package.json, triggers/, creates/, and searches/ folders.
Verification: zapier apps lists your new app.
2. Configure authentication
Zapier supports several auth schemes; for Odoo, we use Custom Auth where the user provides URL, database name, login, and API key. Edit authentication.js:
const test = async (z, bundle) => {
const response = await z.request({
method: 'POST',
url: `${bundle.authData.url}/xmlrpc/2/common`,
body: `<?xml version='1.0'?>
<methodCall>
<methodName>authenticate</methodName>
<params>
<param><value><string>${bundle.authData.db}</string></value></param>
<param><value><string>${bundle.authData.login}</string></value></param>
<param><value><string>${bundle.authData.apikey}</string></value></param>
<param><value><struct/></value></param>
</params>
</methodCall>`,
headers: { 'Content-Type': 'text/xml' },
});
if (response.content.includes('<fault>')) {
throw new z.errors.RefreshAuthError('Invalid Odoo credentials');
}
// Parse the integer UID from the XML response
const uidMatch = response.content.match(/<int>(\d+)<\/int>/);
if (!uidMatch) throw new z.errors.RefreshAuthError('Cannot parse Odoo response');
return { uid: parseInt(uidMatch[1]) };
};
module.exports = {
type: 'custom',
fields: [
{ key: 'url', label: 'Odoo URL', helpText: 'e.g., https://erp.acme.com', required: true, type: 'string' },
{ key: 'db', label: 'Database Name', required: true, type: 'string' },
{ key: 'login', label: 'Login (email)', required: true, type: 'string' },
{ key: 'apikey', label: 'API Key', required: true, type: 'password' },
],
test,
connectionLabel: '{{bundle.authData.login}} on {{bundle.authData.url}}',
};
Verification: in the Zapier UI, when you connect a test Odoo account, the test passes and the connection label shows the user's email.
3. Build the "New Sale Order" trigger (polling)
Polling triggers are simplest to start. Zapier polls every 5 to 15 minutes and asks "what's new?". Create triggers/sale_order.js:
const buildXmlRpcCall = (db, uid, apikey, model, method, args) => `<?xml version='1.0'?>
<methodCall>
<methodName>execute_kw</methodName>
<params>
<param><value><string>${db}</string></value></param>
<param><value><int>${uid}</int></value></param>
<param><value><string>${apikey}</string></value></param>
<param><value><string>${model}</string></value></param>
<param><value><string>${method}</string></value></param>
${args}
</params>
</methodCall>`;
const triggerNewSaleOrder = async (z, bundle) => {
const argsXml = `
<param><value><array><data>
<value><array><data>
<value><array><data>
<value><string>state</string></value>
<value><string>=</string></value>
<value><string>sale</string></value>
</data></array></value>
</data></array></value>
</data></array></value></param>
<param><value><struct>
<member><name>fields</name><value><array><data>
<value><string>id</string></value>
<value><string>name</string></value>
<value><string>partner_id</string></value>
<value><string>date_order</string></value>
<value><string>amount_total</string></value>
<value><string>state</string></value>
</data></array></value></member>
<member><name>limit</name><value><int>100</int></value></member>
<member><name>order</name><value><string>id desc</string></value></member>
</struct></value></param>`;
const response = await z.request({
method: 'POST',
url: `${bundle.authData.url}/xmlrpc/2/object`,
body: buildXmlRpcCall(bundle.authData.db, bundle.authData.uid, bundle.authData.apikey,
'sale.order', 'search_read', argsXml),
headers: { 'Content-Type': 'text/xml' },
});
// Parse the XML response into JS objects (use a library like xml2js in real code)
return parseXmlrpcResponse(response.content);
};
module.exports = {
key: 'new_sale_order',
noun: 'Sale Order',
display: {
label: 'New Sale Order',
description: 'Triggers when a sale order moves to confirmed state.',
},
operation: {
perform: triggerNewSaleOrder,
sample: {
id: 42,
name: 'SO00042',
partner_id: [7, 'Acme Corp'],
date_order: '2026-05-04 14:30:00',
amount_total: 1500.00,
state: 'sale',
},
},
};
Register it in index.js under the triggers key. Verification: in Zapier UI, create a Zap with this trigger, hit "Test trigger", and you see real Odoo sale orders.
4. Build the "Create Contact" action
Actions push data into Odoo. Create creates/contact.js:
const createContact = async (z, bundle) => {
const argsXml = `
<param><value><array><data>
<value><struct>
<member><name>name</name><value><string>${escapeXml(bundle.inputData.name)}</string></value></member>
${bundle.inputData.email ? `<member><name>email</name><value><string>${escapeXml(bundle.inputData.email)}</string></value></member>` : ''}
${bundle.inputData.phone ? `<member><name>phone</name><value><string>${escapeXml(bundle.inputData.phone)}</string></value></member>` : ''}
${bundle.inputData.is_company ? '<member><name>is_company</name><value><boolean>1</boolean></value></member>' : ''}
</struct></value>
</data></array></value></param>`;
const response = await z.request({
method: 'POST',
url: `${bundle.authData.url}/xmlrpc/2/object`,
body: buildXmlRpcCall(bundle.authData.db, bundle.authData.uid, bundle.authData.apikey,
'res.partner', 'create', argsXml),
headers: { 'Content-Type': 'text/xml' },
});
const idMatch = response.content.match(/<int>(\d+)<\/int>/);
return { id: parseInt(idMatch[1]), name: bundle.inputData.name };
};
module.exports = {
key: 'create_contact',
noun: 'Contact',
display: { label: 'Create Contact', description: 'Creates a contact in Odoo.' },
operation: {
inputFields: [
{ key: 'name', required: true, type: 'string' },
{ key: 'email', type: 'string' },
{ key: 'phone', type: 'string' },
{ key: 'is_company', type: 'boolean' },
],
perform: createContact,
sample: { id: 42, name: 'John Doe' },
},
};
Verification: in a Zap, set the action to "Create Contact" with name "Test User", run it, and the contact appears in Odoo.
5. Add searches
Searches are how Zapier looks up existing records before creating new ones (preventing duplicates). Build a "Find Contact by Email" search using search_read with an email domain filter. Add to searches/contact.js. Verification: a Zap that says "If contact found, update; else create" works correctly.
6. Switch from polling to webhooks (advanced)
Polling has up to a 15-minute lag. For real-time, use Zapier's REST Hooks: Zapier registers a webhook URL with your Odoo, and Odoo POSTs to it when something happens.
Add a small Odoo module that creates automated.action rules pushing to a configurable URL. The Zapier subscribe handler tells Odoo where to push:
const subscribeHook = async (z, bundle) => {
// bundle.targetUrl is the URL Zapier wants you to POST to
const response = await z.request({
method: 'POST',
url: `${bundle.authData.url}/zapier/subscribe`,
headers: { 'X-Odoo-API-Key': bundle.authData.apikey, 'Content-Type': 'application/json' },
body: JSON.stringify({
event: 'sale.order.confirmed',
target_url: bundle.targetUrl,
}),
});
return response.data; // { id: hookId }
};
Verification: confirm an SO in Odoo, watch Zapier receive the webhook within 1 second.
7. Test end-to-end
Build a sample Zap: when a new sale order is confirmed in Odoo, create a row in a Google Sheet. Run it. Confirm an SO. The Sheet row appears within 5 minutes (polling) or 5 seconds (webhook). Verification: success across at least 5 sample Zaps with different downstream apps.
8. Submit for public listing (optional)
If you want the app available to all Zapier users (not just your account), submit it for review at developer.zapier.com. Zapier reviews for ~1 to 2 weeks, requires a privacy policy URL, support email, and 5 to 10 successful test Zaps run by Zapier's QA team.
Common mistakes
- Storing the API key in the URL or query string. Always use header or body. URLs get logged.
- Not parsing XML-RPC fault responses. Zapier shows opaque "execution failed" errors. Always check for
<fault>and surface the message. - Polling too aggressively. Zapier limits polls to once per 5 minutes minimum on free tier, every 1 minute on premium. Don't try to game it.
- Hardcoding the database name. Different Odoo instances have different DB names. Always read from
bundle.authData.db. - Skipping
escapeXmlon user input. An ampersand in a contact name breaks XML-RPC parsing.
Going further
JSON-RPC alternative: Odoo also exposes a JSON-RPC endpoint at /jsonrpc. It's easier to construct payloads but slightly less battle-tested than XML-RPC. Pick one and stick to it.
Bulk operations: for high-volume imports, Zapier has a "Looping by Zapier" action that lets you process arrays. Pair with the Odoo create method (which accepts a list).
Custom fields: most clients need to push Odoo's custom fields. Add a "Get Field List" search step that pulls available fields per model so users can map them dynamically.
Per-tenant Zapier apps: for SaaS resellers, build a custom Zapier app per tenant where the auth is the tenant's Odoo URL + API key, scoped to that tenant only.
For full Zapier app development including App Store listing, multi-tenant auth, and 50+ trigger/action coverage, ECOSIRE custom Odoo development builds the entire app. Pair this with how to build a REST API endpoint in Odoo for the Odoo-side API plumbing.
Frequently Asked Questions
Why not use Make.com (formerly Integromat) instead?
Make is more powerful per-Zap but smaller user base. Zapier has 5,000+ apps; Make has 1,500+. Most ECOSIRE clients want the Zapier marketplace reach. The connector pattern in this recipe works on Make too (the auth + REST call structure is identical).
Can I build the Zapier app without coding?
Zapier has a "Visual Builder" alternative that requires no code. It works for simple REST APIs but cannot handle XML-RPC. For Odoo's XML-RPC, the CLI approach is mandatory.
How do I handle Odoo Multi-Company in the Zapier app?
Add a "Company" input field to your auth fields and pass it as the context parameter to every XML-RPC call: {'force_company': company_id}. The Zapier user picks which company at connection time.
What is the rate limit?
Zapier's free plan: 100 tasks/month. Starter: 750. Professional: 2,000+. Each trigger fire + action run = 1 task. For high-volume Odoo flows, the Team or Company plans are needed.
For complex multi-app automation including conditional branching and AI-augmented data transformation, ECOSIRE custom integrations build the full automation stack. Or read how to integrate Twilio SMS with Odoo for an example of a direct (non-Zapier) integration.
Rédigé par
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transformez votre entreprise avec Odoo ERP
Implémentation, personnalisation et assistance expertes d'Odoo pour rationaliser vos opérations.
Articles connexes
Comment ajouter un bouton personnalisé à une vue de formulaire Odoo (2026)
Ajoutez des boutons d'action personnalisés aux vues de formulaire Odoo 19 : méthode d'action Python, héritage des vues, visibilité conditionnelle, boîtes de dialogue de confirmation. Testé en production.
Comment ajouter un champ personnalisé dans Odoo sans Studio (2026)
Ajoutez des champs personnalisés via le module personnalisé dans Odoo 19 : héritage de modèle, extension de vue, champs calculés, décisions magasin/non-magasin. Code d'abord, contrôle de version.
Comment ajouter un rapport personnalisé dans Odoo à l'aide d'une mise en page externe
Créez un rapport PDF de marque dans Odoo 19 à l'aide de web.external_layout : modèle QWeb, format papier, liaison d'action. Avec logo imprimé + remplacements de pied de page.