Skip to content

Odoo Bus Service & Real-Time Notifications Guide

DeployMonkey Team · March 24, 2026 11 min read

What is the Bus Service?

Odoo's bus service (implemented by the bus module) provides real-time server-to-client push notifications. When something changes on the server — a new message arrives, a record is updated, or a background task completes — the bus pushes a notification to connected browser clients instantly, without requiring page refreshes or polling.

This powers Odoo's live chat, real-time discuss messaging, notification badges, and presence indicators.

How It Works

Architecture

ComponentRole
bus.bus modelServer-side notification queue (PostgreSQL table)
Longpolling/WebSocketPersistent connection from browser to server
bus_service (JS)Client-side service that receives and dispatches notifications
ChannelsLogical groupings for notification routing

Flow

  1. Server code calls self.env['bus.bus']._sendone(channel, type, message)
  2. A record is created in the bus_bus table
  3. PostgreSQL NOTIFY triggers the longpolling/WebSocket controller
  4. The controller pushes the notification to all clients subscribed to that channel
  5. The client-side bus_service dispatches the notification to registered handlers

Server Side: Sending Notifications

Send to a Specific User

# Send to a specific user's partner
self.env['bus.bus']._sendone(
    self.env.user.partner_id,    # channel (partner record)
    'my_module.update',          # notification type
    {                            # payload (JSON-serializable dict)
        'message': 'Your report is ready.',
        'record_id': record.id,
        'action': 'download',
    },
)

Send to All Users

# Broadcast to all connected users
self.env['bus.bus']._sendone(
    'broadcast',                 # special channel name
    'my_module.announcement',
    {'message': 'System maintenance in 30 minutes.'},
)

Send to Multiple Channels

# Send to multiple users at once
channels = [(partner, 'my_module.update', payload)
            for partner in partner_ids]
self.env['bus.bus']._sendmany(channels)

Client Side: Receiving Notifications

In an OWL Component

/** @odoo-module **/
import { Component, onWillStart, onWillUnmount } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";

class LiveDashboard extends Component {
    static template = "my_module.LiveDashboard";

    setup() {
        this.busService = useService("bus_service");
        this.orm = useService("orm");

        // Subscribe to notification channel
        this.busService.addChannel("my_module_live");

        // Listen for specific notification types
        this.busService.addEventListener(
            "notification",
            this.onNotification.bind(this),
        );

        onWillUnmount(() => {
            this.busService.removeEventListener(
                "notification",
                this.onNotification.bind(this),
            );
        });
    }

    onNotification({ detail: notifications }) {
        for (const { payload, type } of notifications) {
            if (type === "my_module.update") {
                this.handleUpdate(payload);
            }
        }
    }

    handleUpdate(payload) {
        // Update component state with new data
        console.log("Received update:", payload);
        // Refresh dashboard data
        this.loadData();
    }
}

Common Use Cases

Live Record Updates

Notify clients when a record they are viewing has been modified by another user:

# In the model's write method
def write(self, vals):
    result = super().write(vals)
    for record in self:
        self.env['bus.bus']._sendone(
            'my_module_record_%s' % record.id,
            'my_module.record_updated',
            {'record_id': record.id, 'fields': list(vals.keys())},
        )
    return result

Background Task Completion

Notify the user when a long-running task finishes:

# In the cron or background task
def _process_report(self):
    # ... long processing ...
    report_url = self._generate_report()
    
    # Notify the user who requested the report
    self.env['bus.bus']._sendone(
        self.requested_by.partner_id,
        'my_module.report_ready',
        {
            'title': 'Report Ready',
            'message': 'Your sales report has been generated.',
            'url': report_url,
        },
    )

Progress Indicators

# Send progress updates during long operations
for i, record in enumerate(records):
    record.process()
    if i % 10 == 0:  # Update every 10 records
        self.env['bus.bus']._sendone(
            self.env.user.partner_id,
            'my_module.progress',
            {'current': i + 1, 'total': len(records)},
        )
        self.env.cr.commit()  # Commit so the bus notification is sent

Longpolling vs WebSocket

FeatureLongpollingWebSocket
ConnectionHTTP request held openPersistent bidirectional
EfficiencyLess efficient (reconnects)More efficient (single connection)
ConfigurationWorks out of the boxRequires gevent worker mode
Proxy supportWidely supportedNeeds proxy WebSocket support

Odoo 17+ prefers WebSocket when available. Configure with --gevent-port for the WebSocket/longpolling worker.

Infrastructure Requirements

  • Separate gevent worker process for longpolling (--gevent-port 8072)
  • Nginx proxy must forward /websocket to the gevent worker
  • PostgreSQL LISTEN/NOTIFY for inter-process communication
  • If using multiple Odoo workers, all must connect to the same PostgreSQL instance

Common Pitfalls

  • Missing gevent worker — Without a dedicated longpolling worker, bus notifications do not work. Configure --gevent-port in your Odoo startup.
  • Nginx WebSocket proxy — Nginx must include proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "Upgrade" for the /websocket path.
  • cr.commit() for progress — Bus notifications are only sent after the transaction commits. For progress updates, commit periodically.
  • Payload size — Keep notification payloads small. Send IDs and let the client fetch full data, rather than sending entire records.