# Home Assistant Event Bus Architecture: How Events Are Dispatched

> Understand the Home Assistant event bus architecture. Learn how events are dispatched efficiently using asynchronous listeners for non-blocking pub/sub communication. Explore the core object and integration flow.

- Repository: [Home Assistant/core](https://github.com/home-assistant/core)
- Tags: architecture
- Published: 2026-02-28

---

**Home Assistant uses a single, thread-safe EventBus attached to the `HomeAssistant` core object to dispatch events via asynchronous listener jobs, enabling non-blocking pub/sub communication across all integrations.**

The event bus architecture in Home Assistant serves as the central messaging backbone of the platform, allowing integrations, services, and core components to communicate without direct dependencies. Implemented in [`homeassistant/core.py`](https://github.com/home-assistant/core/blob/main/homeassistant/core.py) within the `home-assistant/core` repository, this architecture guarantees thread-safe event registration and dispatch through a combination of listener registries, job wrappers, and context-aware Event objects.

## Core Components of the Event Bus

### The EventBus Class and Listener Registry

The `EventBus` class definition resides at [line 1422 of [`homeassistant/core.py`](https://github.com/home-assistant/core/blob/main/homeassistant/core.py)](https://github.com/home-assistant/core/blob/dev/homeassistant/core.py#L1422). It maintains a `_listeners` registry implemented as a `defaultdict` that maps specific event types to lists of listener jobs (lines 1427–1435). This registry stores tuples containing a `HassJob` wrapper and an optional filter, enabling the bus to execute callbacks in the correct execution context.

A special `MATCH_ALL` list within the registry captures listeners that subscribe to every event type, allowing system-wide event monitoring.

### Event Objects and Context Metadata

Every dispatched message travels as an `Event` instance, defined at [line 1283](https://github.com/home-assistant/core/blob/dev/homeassistant/core.py#L1283). These objects carry:

- **event_type**: The identifier string (e.g., `"state_changed"`)
- **data**: The payload dictionary
- **origin**: An `EventOrigin` enum indicating local or remote source
- **context**: Execution context for tracking event chains
- **time_fired**: UTC timestamp

### Thread Safety and HassJob Wrappers

To prevent blocking the event loop, the bus wraps all callbacks in `HassJob` objects. When an event fires, the bus calls `self.hass.async_run_hass_job`, ensuring the listener executes in Home Assistant’s executor thread pool. This design isolates slow user code from the core async loop.

## How Events Are Dispatched in Home Assistant

### Listener Registration via async_listen

Registering a listener stores a job reference in the registry without blocking:

```python
listener = hass.bus.async_listen("my_event", my_callback)

```

The `async_listen` method creates a `HassJob` from the provided callback or coroutine and appends it to the `_listeners` dictionary under the specified event type key.

### Firing Events from Any Thread

The EventBus provides two entry points for thread safety:

**From outside the event loop**, use `EventBus.fire` (lines 1556–1568). This method marshals the call into the event loop using `call_soon_threadsafe`, ensuring thread-safe access from background threads.

**From inside the event loop**, use `await hass.bus.async_fire` (lines 1570–1588). This performs validation via `_verify_event_type_length_or_raise` before calling the internal dispatcher.

### Internal Dispatch Pipeline

The `async_fire_internal` method (lines 1590–1600) executes the actual dispatch:

1. **Validates** the event type string length.
2. **Instantiates** an `Event` object with the current timestamp and context.
3. **Iterates** over listeners registered for that specific type plus the `MATCH_ALL` subscribers.
4. **Schedules** each `HassJob` via `async_run_hass_job` for execution.

Because dispatching schedules jobs rather than awaiting them directly, a slow listener cannot block subsequent event processing.

## Special Listener Patterns

### One-Time Listeners with async_listen_once

For single-use callbacks, the `_OneTimeListener` wrapper (lines 9395–9402) automatically removes itself from the registry before executing the user callback. This prevents memory leaks for one-off event handling:

```python
hass.bus.async_listen_once("my_custom_event", handle_once)

```

### Match-All Event Subscriptions

To receive every event on the bus, subscribe using the wildcard `"*"` (internally `MATCH_ALL`):

```python
hass.bus.async_listen("*", all_events_handler)

```

This registers the listener in the special match-all list that `async_fire_internal` checks on every dispatch.

## Practical Implementation Examples

### Listening to Custom Events

```python
def my_handler(event):
    _LOGGER.info("Received %s with data %s", event.event_type, event.data)

# Register persistent listener

hass.bus.async_listen("my_custom_event", my_handler)

```

### Firing Events from Integrations

From async context:

```python
await hass.bus.async_fire(
    "my_custom_event",
    {"value": 42, "source": "sensor"},
    origin=EventOrigin.local,
)

```

From a background thread:

```python
hass.bus.fire(
    "my_custom_event",
    {"value": 42, "source": "sensor"},
)

```

### One-Time Event Handling

```python
async def handle_once(event):
    _LOGGER.debug("One-time handler received: %s", event.data)

# Auto-removes after first execution

hass.bus.async_listen_once("startup_complete", handle_once)

```

## Summary

- **Single EventBus instance**: Lives on `hass.bus` in [`homeassistant/core.py`](https://github.com/home-assistant/core/blob/main/homeassistant/core.py) and manages all system communication through a centralized registry.
- **Thread-safe dispatch**: `fire()` marshals calls to the event loop, while `async_fire()` validates and dispatches directly, both utilizing `async_fire_internal` for execution.
- **Job isolation**: Listeners execute as `HassJob` instances in the executor thread pool, preventing slow callbacks from blocking the event bus.
- **Flexible subscription**: Supports persistent listeners, one-time auto-removing listeners via `_OneTimeListener`, and catch-all subscriptions using `MATCH_ALL`.

## Frequently Asked Questions

### What is the EventBus in Home Assistant?

The EventBus is a centralized pub/sub messaging system implemented in [`homeassistant/core.py`](https://github.com/home-assistant/core/blob/main/homeassistant/core.py) that allows components to communicate by firing events and registering listeners. It maintains a thread-safe registry of callbacks and ensures all listeners execute asynchronously without blocking the core event loop.

### How do I fire an event from a custom integration?

Use `hass.bus.async_fire(event_type, data)` when running inside the event loop, or `hass.bus.fire(event_type, data)` when calling from a background thread. Both methods create an `Event` object and dispatch it to registered listeners through the internal `async_fire_internal` pipeline.

### Are event listeners thread-safe?

Yes. The EventBus uses `HassJob` wrappers and `async_run_hass_job` to execute all listener callbacks in Home Assistant’s executor thread pool. This guarantees that user-provided callback code never blocks the async event loop, regardless of which thread registered the listener.

### What is the difference between async_listen and async_listen_once?

`async_listen` registers a persistent callback that receives every occurrence of an event type until manually removed, while `async_listen_once` uses the `_OneTimeListener` wrapper to automatically unregister the callback after it fires the first time, making it ideal for initialization or one-off synchronization tasks.