Connect Odoo to WhatsApp with the API
Send Odoo notifications to WhatsApp and turn inbound messages into Odoo records — the models, the JSON-RPC calls, and the webhook patterns that make it reliable.
ODXProxy Team · Jul 13, 2026 · 8 min read

"Can I connect Odoo to WhatsApp?" comes up constantly — order confirmations pushed to a customer's phone, a leads bot that files enquiries straight into the CRM, delivery updates that don't need an email. Odoo Enterprise ships a native WhatsApp module, but if you're on Community, or your logic is more custom than templates allow, you build the bridge yourself against two APIs: WhatsApp Business Cloud on one side and the Odoo External API on the other. This guide shows both directions — Odoo → WhatsApp notifications and WhatsApp → Odoo records — with the exact calls and the webhook patterns that keep it reliable. Every Odoo call runs over JSON-RPC through a single endpoint.
The two APIs you're bridging
- WhatsApp Business Cloud API — Meta-hosted, part of the Graph API. You send messages with a
POSTtohttps://graph.facebook.com/<version>/<phone-number-id>/messages, authenticated with a bearer access token, and you receive messages via a webhook you register in the Meta app dashboard. Business-initiated conversations must use pre-approved message templates; free-form replies are only allowed inside the 24-hour customer service window. - Odoo External API — the ERP side, reached here through the ODXProxy JSON-RPC endpoint.
Your integration is a small service in the middle: it listens for Odoo events to push messages out, and listens for WhatsApp webhooks to write records in.
Two directions, two flows
| Flow | Trigger | Direction |
|---|---|---|
| Notifications | An Odoo event (order confirmed, invoice posted) | Odoo → WhatsApp |
| Inbound capture | A customer sends a WhatsApp message | WhatsApp → Odoo |
They share nothing but the customer's phone number, which is the join key between the two systems. Get phone-number matching right and the rest is plumbing.
The Odoo models you'll touch
res.partner— contacts. Thephoneandmobilefields are how you match a WhatsApp sender to an existing customer.crm.lead— leads and opportunities, the natural home for an inbound enquiry.sale.order/account.move— the orders and invoices whose state changes trigger outbound notifications.mail.message— Odoo's chatter, if you want the conversation logged against the contact.
You reach all of them through the nine actions of the External API. If these models are unfamiliar, Odoo search_read, in depth and create, write, unlink cover the read and write mechanics used below.
One endpoint, two secrets
Every Odoo call goes to the same route through the proxy:
POST /api/odoo/executeKeep the two proxy-side secrets straight — conflating them is the usual cause of a stray 401:
x-api-key— the proxy's key, an HTTP header authenticating your integration to the proxy.odoo_instance.api_key— the Odoo user's key, sent per request in the body, authenticating the ERP call itself.
Neither is the WhatsApp access token — that belongs to the Meta side. The full proxy credential setup is in How to authenticate to the Odoo API.
Flow 1 — push an Odoo event to WhatsApp
Say a sale.order is confirmed and you want to text the customer. Two steps: read what you need from
Odoo, then hand it to the WhatsApp API.
Read the order and the customer's number in one search_read, following the partner_id relation
with a dotted path so you don't need a second call:
{
"id": "read-order-1",
"action": "search_read",
"model_id": "sale.order",
"params": [[["name", "=", "S00042"]]],
"keyword": { "fields": ["name", "amount_total", "partner_id"], "limit": 1 },
"odoo_instance": {
"url": "https://erp.example.com",
"db": "prod",
"user_id": 2,
"api_key": "<the Odoo user API key>"
}
}Then send a template message from your service to the WhatsApp Cloud API, filling the template variables with the order number and total. Because this is business-initiated, it must be a pre-approved template — free text would be rejected outside the 24-hour window.
To trigger it, either poll Odoo for state changes on a short interval, or — better — have Odoo push the event to you with an outgoing webhook, so you only call out when something actually changed. Setting those up is covered in The Odoo webhook API.
Flow 2 — turn an inbound WhatsApp message into an Odoo record
When a customer messages your WhatsApp number, Meta delivers the message to your webhook. Your handler matches the sender to a contact and files the enquiry. Do it idempotently.
Step 1 — match the sender to a contact. WhatsApp gives you the sender's phone number; look for an
existing res.partner before creating anything:
{
"id": "find-partner-1",
"action": "search_read",
"model_id": "res.partner",
"params": [["|", ["mobile", "=", "+15551234567"], ["phone", "=", "+15551234567"]]],
"keyword": { "fields": ["id", "name"], "limit": 1 },
"odoo_instance": {
"url": "https://erp.example.com",
"db": "prod",
"user_id": 2,
"api_key": "<the Odoo user API key>"
}
}That domain uses the | prefix operator to match either field — the operator syntax is in
Odoo domain filters explained. If the result is empty, create a new
partner from the WhatsApp profile name and number; otherwise reuse the id you got back.
Step 2 — file the enquiry as a lead. Create a crm.lead linked to that partner, carrying the
message body so a salesperson sees the context:
{
"id": "create-lead-1",
"action": "create",
"model_id": "crm.lead",
"params": [{
"name": "WhatsApp enquiry from Dana Lee",
"partner_id": 57,
"contact_name": "Dana Lee",
"description": "Do you ship the walnut desk to Portugal?"
}],
"odoo_instance": {
"url": "https://erp.example.com",
"db": "prod",
"user_id": 2,
"api_key": "<the Odoo user API key>"
}
}To avoid a duplicate lead per message in the same conversation, store the WhatsApp conversation or
message id on the record (a field like x_wa_conversation_id) and search_count on it before creating
— the same external-id idempotency habit every reliable integration leans on. The write mechanics are
in create, write, unlink.
Making it a bot, not just a bridge
The moment you want to reply intelligently — answer "where's my order?" or "do you have this in
stock?" — the pattern becomes an LLM with tools. The model reads the inbound WhatsApp text, decides
which Odoo call to make, and you execute it through the same /api/odoo/execute endpoint. Reads like
order status map to search_read; actions like booking a callback map to call_method. That tool-use
loop, with the exact function-calling schema, is laid out in
Odoo + LLM function calling. The WhatsApp layer just becomes the
transport that carries the user's turn in and the model's answer back out.
Webhook reliability
Both directions lean on webhooks, and two rules keep them trustworthy:
- Verify the signature. WhatsApp signs each webhook body with HMAC-SHA256 and sends it in the
X-Hub-Signature-256header; recompute it with your app secret and compare before you act. (You also answer Meta's one-timehub.challengeverification when you register the webhook.) - Treat delivery as at-least-once. Meta retries webhooks, so the same message can arrive twice. Your handler must be idempotent — which is why Step 2 stores a message id and checks it before creating.
200 from the proxy does not mean Odoo accepted the write. Odoo logic errors — a missing required field, a blocked access right — come back with HTTP 200 and a populated error object. Check the HTTP status first, then check the body's error field before you acknowledge the WhatsApp webhook.The full two-layer error model — proxy failures versus Odoo logic errors, and how to retry each — is in Odoo API error handling.
A realistic scope note
If you run Odoo Enterprise and your needs are standard — templated order and invoice notifications — try the native WhatsApp module first; it wires the Cloud API into Odoo without code. Build a custom integration when you're on Community, when you need bot logic beyond templates, or when the conversation has to touch models the native module doesn't. When you do, the External API gives you the whole ERP surface, and the patterns above — phone-number matching, external-id idempotency, and the two-step error check — are what keep it from double-filing leads or dropping messages.
Where to go next
- Nail the reads and writes with Odoo search_read, in depth and create, write, unlink.
- Build the reply logic with Odoo + LLM function calling.
- Trigger outbound messages from Odoo events with The Odoo webhook API.
- Harden every handler with Odoo API error handling.
- Read the full request and response contract in the API reference, or skip the envelope boilerplate with the Python SDK.