Integrations

# Odoo Integration

Connect your Odoo accounting system to Zazu and push your contacts, invoices, and journal entries straight into Odoo's ledger — no manual re-keying. About 5 minutes if Odoo is already set up.

## What you'll need

Before you start, log in to your Odoo instance in a browser and have these ready:

![Odoo login screen](https://zazu.africa/assets/integrations/odoo/01-login-99536219.webp)

| Value | What it is |
| --- | --- |
| **Instance URL** | The address of your Odoo, e.g. https://your-company.odoo.com (HTTPS required) |
| **Database name** | The Odoo database to connect (Odoo Online: usually your subdomain) |
| **Login** | The email/login you sign in to Odoo with |
| **API key** | A secret token you generate in Odoo (step 2) |

> **Note:** Zazu never stores your Odoo password — it uses an API key, which you can revoke at any time from Odoo without changing your password.

## Prerequisites in Odoo

The integration writes to Odoo's accounting models `res.partner` and `account.move`, so your database needs:

1. The Accounting (or Invoicing) app installed.
2. A chart of accounts loaded. For Moroccan companies, install the **Morocco - Accounting (l10n_ma)** localization for the Moroccan chart and standard VAT taxes.

## A valid HTTPS certificate is required

Zazu only talks to Odoo over HTTPS, and the certificate must be valid and trusted — not self-signed, not expired, issued for the exact hostname. This applies to Odoo 16, 17, and 18, and to inbound webhooks too.

**Odoo Online (*.odoo.com) **already has a valid certificate — skip to the API key step.

**Self-hosted Odoo **often runs on plain HTTP or a self-signed certificate. Install a free Let's Encrypt certificate behind a reverse proxy:

```console
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d erp.your-company.com
```

## Generate an API key

Zazu connects with an API key rather than your password:

1. Turn on developer mode (Settings → Developer Tools → Activate the developer mode).![Activate developer mode in Odoo Settings](https://zazu.africa/assets/integrations/odoo/02-developer-mode-32fa7e8b.webp)
2. Open your avatar → My Profile (or Preferences) → the Account Security tab.![Account Security tab in My Preferences](https://zazu.africa/assets/integrations/odoo/03-account-security-d96f9690.webp)
3. In API Keys, click New API Key, name it (e.g. “Zazu”), and generate it.
4. Copy the key immediately — Odoo shows it only once.

> **Tip:** The key grants the same access as the user you generated it for, so use a user with the accounting permissions you're comfortable with. Revoke it any time from the same screen.

## Connect in Zazu

Go to Settings → Integrations → Odoo and fill in the form: Deployment (Odoo Online vs Self-hosted), Instance URL (https://…), Database name, Login, and API key. Click Connect — Zazu makes a live test call to verify the credentials before saving, then fetches your chart of accounts and tax rates.

## Map your accounts

Before invoices and journal entries can be pushed, match Zazu's concepts to your own chart of accounts: a sales revenue account, a clearing account (journal entries only), and a tax code per VAT rate you invoice with. Open the integration's settings page — the Account mapping card lists everything that still needs a selection, with the options taken straight from your synced chart of accounts.

Until the mapping is complete, pushes are held back safely — nothing is ever posted to a guessed account.

## Optional: inbound webhooks

Optional — the integration re-syncs on a schedule without it. Webhooks make that refresh instant. Available on Odoo 17/18 (Odoo 16 has no Automation-Rule webhooks; it falls back to periodic re-syncing).

Create an Automation Rule (On Save) on both the Chart of Accounts `account.account` and Taxes `account.tax` models, each with a Webhook action pointing at `https://<your-zazu-host>/webhooks/odoo` and an `X-Zazu-Webhook-Secret` header.

> **Send a payload Zazu can match:** Odoo's default webhook body only contains _id/_model, which Zazu can't match to a connection. Send a custom payload including database and host, or every delivery is rejected with 401.

```json
{
  "database": "your-company",
  "host": "your-company.odoo.com",
  "model": "account.account",
  "record_id": 42,
  "event": "write"
}
```

## Verifying the webhook sync

Any save on the Chart of Accounts fires the accounts rule, so the simplest way to force a refresh from Odoo is to open any account under **Accounting → Configuration → Chart of Accounts**, make a no-op edit (e.g. re-save the name), and save. Odoo POSTs to the webhook URL and Zazu re-syncs your accounts and taxes within seconds.

After you trigger a change, the integration page's **last synced** time should update within a few seconds. If it doesn't, work through these in order — they cover almost every case:

1. **Wrong or missing secret header. **The header name must be exactly `X-Zazu-Webhook-Secret` and the value must match the secret on the integration page character-for-character. A wrong secret is rejected with 401 and nothing syncs.
2. **Payload missing database/host. **If you left Odoo's default payload in place (just `_id`/`_model`), Zazu can't match the event to your connection and rejects it with 401. Use the custom payload shown above.
3. **host doesn't match. **It must be the bare hostname, identical to the host in your Instance URL — no scheme, no port, no trailing slash.
4. **Certificate problem (self-hosted). **The webhook may reach Zazu, but receiving it makes Zazu re-fetch your accounts and taxes from Odoo — and that call rejects expired, self-signed, or hostname-mismatched certificates (see the certificate requirement above), so nothing updates.
5. **Only one rule set up. **Tax changes need the `account.tax` rule; account changes need the `account.account` rule. Confirm both exist.

## What gets pushed

| Zazu record | Becomes in Odoo |
| --- | --- |
| Customer | `res.partner (a contact)` |
| Invoice | `account.move (a customer invoice, in draft)` |
| Posting | `account.move (a journal entry)` |

Each push is idempotent — Zazu tags every record it creates with a deterministic identifier (ir.model.data xmlid), so a retry never creates a duplicate in your books.

## Troubleshooting

**“Odoo accounting is not installed.” **Your database doesn't have the Accounting app / chart of accounts. Install **Accounting** and the **Morocco - Accounting (l10n_ma)** localization, then reconnect.

**“Incompatible companies” when pushing an invoice. **Your Odoo has multiple companies and the mapped account/tax belongs to a different one than the connected user's active company. Switch the connected user's active company to the one that owns your Moroccan chart, then re-sync.

**“Could not connect” / the connection test fails. **Check that the URL is HTTPS and reachable, the database name is exactly right (case-sensitive), and the API key was copied without extra spaces. Regenerate the key if in doubt. On a self-hosted server, the most common cause is the TLS certificate.

**“Could not connect” on a self-hosted server (certificate error). **Zazu rejects self-signed, expired, or hostname-mismatched certificates. If your browser shows a certificate warning on the Odoo URL, Zazu won't connect either — install a free, trusted Let's Encrypt certificate (see the certificate requirement above), then reconnect. This is the same certificate that lets inbound webhooks work.

**The API key stopped working. **API keys are tied to a specific database. If your Odoo database was restored, renamed, or recreated, generate a fresh key.

## Good to know

- **Odoo versions: **the integration is tested against Odoo 16, 17, and 18. It uses Odoo's stable JSON-RPC endpoint, which works across all supported versions.
- **Cloud and self-hosted **are both supported — the only difference is the URL and database you enter.
- **Security: **your API key is stored encrypted, every outbound call is logged, and you can disconnect (which clears the stored credentials) at any time from the integration page.