ml-connector
OdooBILL

Odoo and BILL integration

Odoo runs your accounting and purchasing. BILL automates your accounts payable. Connecting them keeps supplier records in agreement and moves purchase orders from Odoo directly into BILL for approval and payment, without manual re-entry. When BILL records a payment, the entry flows back into Odoo so your general ledger reflects cash spent. ml-connector handles the different authentication models on each side and syncs on a schedule you set.

How Odoo works

Odoo exposes purchase orders, vendors (partners), payments, GL accounts, and products through XML-RPC and JSON-2 REST APIs. Older Odoo versions use XML-RPC over HTTP POST to {base_url}/xmlrpc/2/common and {base_url}/xmlrpc/2/object; Odoo 19 and later support JSON-2 over HTTP POST with API key authentication via Bearer token. The base URL is customer-specific: Odoo Online uses https://<subdomain>.odoo.com, Odoo.sh uses https://<subdomain>.odoo.sh, and self-hosted instances use a custom domain. Odoo has no production-grade webhooks in core, so records are read by polling with a write_date filter and high-water-mark timestamp to capture changes. API calls respect the integration user's Odoo access rights and record rules. External API access requires Odoo Custom pricing plan.

How BILL works

BILL exposes vendors, bills, payments, invoices, and chart of accounts through REST endpoints at https://gateway.prod.bill.com/connect with v3 API paths. Authentication uses a session token obtained by calling POST /v3/login with username, password, organizationId, and devKey; sessions expire after 35 minutes of inactivity for standard login or 48 hours for sync token login. BILL also supports webhooks with HMAC-SHA256 signature verification and exponential backoff retries per subscription. Key entities include vendors, bills, payments, invoices, and customers. Purchase orders are not supported in BILL, so only bill-relevant records sync. BILL allows a maximum of 10 webhook subscriptions per organization, and webhook payloads must use minified JSON for HMAC computation.

What moves between them

The main flow is Odoo into BILL. Odoo vendors and purchase orders sync into BILL as vendors and bills on a customer-configured schedule. When BILL records a payment, ml-connector reads the payment event (via webhook if enabled, or by polling the payments endpoint) and writes the payment back into Odoo as an account.payment record, allocated to the matching vendor and GL account. Recurring bills in BILL are read-only, so ml-connector does not write them back to Odoo. Reference data such as chart of accounts and vendor master is validated in both directions to ensure every payment references a GL account and vendor that exist in Odoo.

How ml-connector handles it

ml-connector stores both credential sets encrypted. For Odoo, the integration user's API key is paired with the username to authenticate into the customer's instance; for Odoo 19+, the API key is passed as a Bearer token in the Authorization header, and for older versions the key is included in XML-RPC payloads after obtaining a uid via authenticate(). For BILL, the session token is obtained at the start of each batch by calling POST /v3/login and is refreshed before expiry. Odoo's lack of built-in webhooks means ml-connector polls for new purchase orders and vendors using write_date filters with high-water-mark timestamps, capturing all changes since the last run. BILL webhook events (if enabled) push bill.created and payment.updated notifications; ml-connector verifies the HMAC-SHA256 signature header x-bill-sha-signature using the stored securityKey. BILL sessions expire after 35 minutes, so ml-connector obtains a fresh token before each batch or refreshes mid-batch if a 401 is returned. Vendors are matched by name and domain in both systems; if a vendor exists only in Odoo, it is created in BILL with a reference to the Odoo partner ID. Purchase orders in Odoo are converted to BILL bills, with line items mapped to BILL line fields. Every record carries a full audit trail and can be replayed if a downstream call fails.

A real-world example

A mid-sized supply chain business uses Odoo for accounting and purchasing across two regional offices, and has standardized on BILL for centralized accounts payable and payment orchestration. Before the integration, the AP team received purchase orders from Odoo, re-entered them manually into BILL, and reconciled BILL payments back to Odoo each month. With Odoo and BILL connected, each purchase order flows automatically into BILL for approval and payment routing, and BILL payments post back into Odoo's general ledger immediately, eliminating manual data entry and month-end reconciliation delays.

What you can do

  • Sync Odoo vendors and purchase orders into BILL as vendor master records and bills, with line-item detail preserved.
  • Read BILL payments via webhook or polling and post them back into Odoo as account.payment records, matched to the originating vendor and GL account.
  • Handle Odoo API key and session-based authentication, refreshing BILL session tokens before expiry to avoid outages.
  • Validate vendors and GL accounts in both systems before syncing, ensuring payments land on accounts that exist in Odoo.
  • Poll Odoo purchase orders and vendors on a schedule, with full audit trails and error replay on failed downstream writes.

Questions

Which direction does data move between Odoo and BILL?
The main flow is Odoo into BILL. Vendors and purchase orders sync from Odoo to BILL on a schedule. When BILL records a payment, ml-connector reads the payment and posts it back into Odoo as an account.payment record, keeping the cash position aligned. Recurring bills in BILL are read-only and do not sync back to Odoo.
How does ml-connector handle Odoo's lack of webhooks?
Odoo has no production-grade webhooks in core, so ml-connector polls purchase orders and vendors using write_date filters with high-water-mark timestamps, capturing changes since the last run. BILL can push payment events via webhook if enabled; ml-connector verifies the HMAC-SHA256 signature on each event to confirm authenticity.
How are BILL session tokens managed, given the 35-minute expiry?
ml-connector obtains a fresh BILL session token at the start of each batch by calling POST /v3/login with stored credentials. If a call returns 401 during a batch, the token is refreshed immediately before retrying, ensuring no payment or bill sync fails due to expiry.

Related integrations

Connect Odoo and BILL

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started