REST API for Webhook
This module provides a standard webhook framework for Odoo with full request/response logging and config-driven outbound push notifications.
Inbound (External → Odoo)
5 REST API routes: create_data, update_data, create_update_data, search_data, call_function
Session-based and API Key authentication
Friendly relational field format: many2one, many2many, one2many resolved by name or id
auto_create support for missing related records
Automatic API log creation per request, with configurable per-route toggle
Request and response preview (first N characters, configurable) stored on the log record
Request and response size (character count) displayed on each log
Full payload stored as JSON attachment (accessible via Full Log button) when preview limit exceeded
Autovacuum cron to purge old logs, with optional chunk-based deletion
Outbound (Odoo → External)
webhook.outbound.rule - config-driven rules: which model + domain → which endpoint
webhook.outbound.mixin - add to any model with a single _inherit line; no per-model code required
Trigger domain: full Odoo domain expression evaluated after write(); webhook fires only when a record transitions into matching the domain
Endpoint source: static URL per rule, or per-record callback_url passed by the external system at create time
Payload fields: JSON list supporting field{sub1,sub2} expansion for relational fields - same syntax as search_data
Outbound calls logged in API Logs (log_type = send) with success/failed state
Table of contents
Configuration
System Parameters
Go to Settings > Technical > Parameters > System Parameters to adjust the following keys:
Key |
Default |
Description |
|---|---|---|
webhook.preview_limit |
2000 |
Maximum characters stored in the preview fields. Payloads longer than this are also saved as a full JSON attachment. |
webhook.create_data_log |
True |
Enable logging for /api/create_data |
webhook.update_data_log |
True |
Enable logging for /api/update_data |
webhook.create_update_data_log |
True |
Enable logging for /api/create_update_data |
webhook.search_data_log |
True |
Enable logging for /api/search_data |
webhook.call_function_log |
True |
Enable logging for /api/call_function |
webhook.rollback_state_failed |
1 |
Roll back the transaction when the API response is not successful |
webhook.rollback_except |
1 |
Roll back the transaction when an unhandled exception occurs |
webhook.ignore_checkcompany_model |
[] |
JSON list of model names excluded from company-scoped record lookup |
Outbound Webhook Rules
Go to Settings > Technical > API Configuration > Outbound Webhook Rules to configure outbound push rules.
Field |
Description |
|---|---|
Model |
The Odoo model to watch (e.g. sale.order) |
Trigger Domain |
Odoo domain evaluated after write(). Webhook fires when a record transitions into matching the domain. Uses the domain widget - select a model first to get field suggestions. |
Endpoint Source |
Static URL - always POST to the configured URL. Record Callback URL - use the callback_url stored from the inbound request. |
Endpoint URL |
Required when Endpoint Source is Static URL. |
Payload Fields |
A JSON object. Static values are sent as-is; {field.path} templates are resolved from the record (dotted paths supported, e.g. {partner_id.name}), recursively at any nesting level. A one2many/many2many field can be expanded into a list of objects: give the key matching the field name a one-item array as value, e.g. "order_line": [{"product": "{product_id.name}"}]. Leave empty to send {"id": <record_id>} only. |
Authorization Header |
Optional Authorization header value sent with every outbound request, e.g. Bearer <token>. |
Usage
Inbound (External → Odoo)
API Logs
Every API call is logged under Settings > Technical > API Configuration > API Logs. Each log record shows:
Request Preview / Response Preview - first N characters of the payload
Request Size / Response Size - total character count
Full Log button - opens the full JSON attachment when payload exceeds the preview limit
Callback URL - URL stored from the inbound request for later outbound push
Authentication
Authenticate via /web/session/authenticate before calling any route:
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"db": "<db_name>",
"login": "<username>",
"password": "<password>"
}
}
Alternative - API Key: send Authorization: Bearer <api_key> on every request. No session call needed.
Relational Field Format
Field type |
Format |
Example |
|---|---|---|
many2one |
{"<lookup_field>": "<value>"} |
{"name": "Customer A"} or {"id": 5} |
many2many |
{"mode": "add"|"replace", "records": [...]} (mode defaults to "replace") |
{"records": [{"name": "Tag1"}]} |
one2many |
[{<field>: <value>, ...}, ...] |
[{"product_id": {"name": "A"}, "qty": 1}] |
Multiple many2many items sharing the same lookup field are batched into a single DB query.
API Routes
1. /api/create_data - create a new record
Pass optional callback_url to enable outbound push when the record’s state changes later.
{
"params": {
"model": "<model name>",
"vals": {
"callback_url": "https://your-system/webhook",
"payload": {
"<field1>": "<value1>",
"<many2one_field_id>": {"name": "<value>"},
"<many2many_field_ids>": {"mode": "replace", "records": [{"name": "<val1>"}]},
"<one2many_field_ids>": [
{"<field>": "<value>", "<nested_m2o_id>": {"name": "<value>"}}
]
},
"auto_create": {
"<many2one_field_id>": {"name": "<value>"}
},
"result_field": ["<field1>"]
}
}
}
2. /api/create_update_data - update if found, create if not
{
"params": {
"model": "<model name>",
"vals": {
"search_key": {"<key_field>": "<value>"},
"payload": {
"<field1>": "<value1>",
"<many2one_field_id>": {"name": "<value>"}
},
"result_field": ["<field1>"]
}
}
}
3. /api/update_data - update an existing record
{
"params": {
"model": "<model name>",
"vals": {
"search_key": {"<key_field>": "<value>"},
"payload": {
"<field1>": "<value1>",
"<many2one_field_id>": {"id": 5},
"<many2many_field_ids>": {"mode": "add", "records": [{"name": "<val1>"}]}
},
"result_field": ["<field1>"]
}
}
}
4. /api/search_data - query records
Use field{subfield1,subfield2} to expand relational fields inline.
{
"params": {
"model": "<model name>",
"vals": {
"payload": {
"search_field": [
"<field1>",
"<m2o_field>{<subfield1>,<subfield2>}",
"<o2m_field>{<subfield1>}"
],
"search_domain": "[('<field>', '<operator>', '<value>')]",
"limit": 10,
"order": "<field1> asc, <field2> desc"
}
}
}
}
5. /api/call_function - call a method on a record
method (str): method name
parameter (dict, optional): keyword arguments
context (dict, optional): merged into env.context before the call
{
"params": {
"model": "account.move",
"vals": {
"search_key": {"id": 26},
"payload": {
"method": "action_post",
"context": {"lang": "th_TH"}
}
}
}
}
Attaching Files
Add attachment_ids at any payload level:
"attachment_ids": [{"name": "<filename>", "datas": "<base64>"}]
Outbound (Odoo → External)
When Odoo performs an action (confirm, validate, etc.), the outbound webhook automatically POSTs updated record data back to the external system - no per-model code required.
Step 1 - Add mixin to the target model
In any private addon, add one _inherit line:
from odoo import models
class SaleOrder(models.Model):
_name = "sale.order"
_inherit = ["sale.order", "webhook.outbound.mixin"]
All outbound behaviour is driven by rules configured in the UI.
Step 2 - Configure an Outbound Webhook Rule
Go to Settings > Technical > API Configuration > Outbound Webhook Rules. See CONFIGURE.md for the full field reference.
Trigger domain examples:
# Simple
[("state", "=", "sale")]
# Multiple conditions
[("state", "=", "done"), ("amount_total", ">", 100)]
# Multiple accepted values
[("state", "in", ["done", "validated"])]
The webhook fires only when a field in the domain is being written and the record matches the full domain after the write. This prevents re-triggering when unrelated fields are edited on an already-matching record.
Payload Fields
payload_fields is always a JSON object. Two kinds of values are supported:
Static value (string, number, bool, nested object/array) - sent as-is
``{field.path}`` template - resolved from the triggering record (dotted paths supported, e.g. {partner_id.name}), returning the raw value at the end of the path. Templates are resolved recursively, so they can appear nested inside objects/arrays at any depth. A many2one field must be followed by an explicit subfield (e.g. {partner_id.name}) - {partner_id} alone returns the record itself, not its name.
{
"request_code": "{name}",
"app": "MyApp",
"data": {"id": "{id}", "state": "{state}", "partner": "{partner_id.name}"}
}
Result posted to the external system:
{
"request_code": "SO001",
"app": "MyApp",
"data": {"id": 3, "state": "sale", "partner": "ABC Co."}
}
Leave payload_fields empty to send {"id": <record_id>} only.
Expanding one2many/many2many fields (line items)
To send a list of objects (e.g. sale order lines), use a key matching the field name, with a one-item array as its value - that single item is the per-line template, applied once for every related record:
{
"request_code": "{name}",
"data": {
"id": "{id}",
"state": "{state}",
"order_line": [
{
"product": "{product_id.name}",
"qty": "{product_uom_qty}",
"price": "{price_unit}"
}
]
}
}
Result posted to the external system:
{
"request_code": "SO001",
"data": {
"id": 3,
"state": "sale",
"order_line": [
{"product": "Product A", "qty": 2.0, "price": 500.0},
{"product": "Product B", "qty": 1.0, "price": 300.0}
]
}
}
This only triggers when the key is an actual one2many/many2many field on the model and the array has exactly one item - any other array is sent as a literal value.
Per-record Callback URL
Pass callback_url in the inbound create_data request. Odoo stores it linked to the created record. When the outbound rule fires with Endpoint Source = Record Callback URL, the system looks up that URL and POSTs to it.
{
"params": {
"model": "sale.order",
"vals": {
"callback_url": "https://ext-system/webhook/so-status",
"payload": {
"partner_id": {"name": "ABC Co."},
"order_line": [{"product_id": {"name": "Product A"}, "product_uom_qty": 1}]
}
}
}
}
When the SO is confirmed → Odoo automatically POSTs to https://ext-system/webhook/so-status.
Outbound Logs
All outbound calls appear in API Logs with Log Type = Send. Failed calls are marked state = failed with the error in the response preview.
Bug Tracker
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed feedback.
Do not contact contributors directly about support or help with technical issues.
Credits
Contributors
Kitti Upariphutthiphong kittiu@ecosoft.co.th
Saran Lim. saranl@ecosoft.co.th
NakrobS nakrobs@ecosoft.co.th
Maintainers
This module is part of the ecosoft-odoo/ecosoft-addons project on GitHub.
You are welcome to contribute.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file odoo_addon_usability_webhooks-18.0.4.1.0-py3-none-any.whl.
File metadata
- Download URL: odoo_addon_usability_webhooks-18.0.4.1.0-py3-none-any.whl
- Upload date:
- Size: 69.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2ca2000ddf8d4ff6b7026875ea757d4247e2c30a78c0bfb630cd68ef39ff660
|
|
| MD5 |
74681647fed760698467090c20b3e5e7
|
|
| BLAKE2b-256 |
b01aba8195057896fd320fd0151c8884fc47363c143f1ab4ab6d0f9a4a9eeb2f
|