The Home Assistant WebSocket API provides a fast, real-time bidirectional communication channel for interacting with Home Assistant. It’s the preferred method for frontends and applications that need live updates.
Overview
The WebSocket API allows you to:
Subscribe to state changes and events in real-time
Call services
Query states and configuration
Execute commands with responses
Receive push notifications for updates
Source: homeassistant/components/websocket_api/__init__.py
Connection
Establishing a Connection
Connect to the WebSocket endpoint:
const ws = new WebSocket ( 'ws://localhost:8123/api/websocket' );
ws . onopen = () => {
console . log ( 'Connected to Home Assistant' );
};
ws . onmessage = ( event ) => {
const message = JSON . parse ( event . data );
console . log ( 'Received:' , message );
};
Authentication Flow
Upon connection, you receive an auth_required message:
{
"type" : "auth_required" ,
"ha_version" : "2026.4.0"
}
Send an authentication message with your long-lived access token:
{
"type" : "auth" ,
"access_token" : "YOUR_LONG_LIVED_ACCESS_TOKEN"
}
If successful, you’ll receive:
{
"type" : "auth_ok" ,
"ha_version" : "2026.4.0"
}
If authentication fails:
{
"type" : "auth_invalid" ,
"message" : "Invalid access token"
}
Create long-lived access tokens in the Home Assistant UI under your user profile.
Command Messages
All commands sent to Home Assistant follow this format:
{
"id" : 1 ,
"type" : "command_name" ,
"param1" : "value1" ,
"param2" : "value2"
}
id: Unique integer to match responses (required)
type: Command type (required)
Additional fields depend on the command
Result Messages
Home Assistant responds with either a success or error result:
Success:
{
"id" : 1 ,
"type" : "result" ,
"success" : true ,
"result" : { ... }
}
Error:
{
"id" : 1 ,
"type" : "result" ,
"success" : false ,
"error" : {
"code" : "not_found" ,
"message" : "Entity not found"
}
}
Error Codes
From homeassistant/components/websocket_api/const.py:
ERR_UNKNOWN_COMMAND: Command is not recognized
ERR_INVALID_FORMAT: Message format is invalid
ERR_NOT_FOUND: Requested resource not found
ERR_NOT_ALLOWED: Permission denied
ERR_HOME_ASSISTANT_ERROR: Internal Home Assistant error
ERR_UNKNOWN_ERROR: Unknown error occurred
ERR_UNAUTHORIZED: Authentication required
ERR_TIMEOUT: Command timed out
ERR_NOT_SUPPORTED: Feature not supported
ERR_TEMPLATE_ERROR: Template rendering error
ERR_SERVICE_VALIDATION_ERROR: Service call validation failed
Common Commands
Subscribe to Events
Subscribe to specific event types:
{
"id" : 2 ,
"type" : "subscribe_events" ,
"event_type" : "state_changed"
}
Subscribe to all events (omit event_type):
{
"id" : 3 ,
"type" : "subscribe_events"
}
Event Messages:
{
"id" : 2 ,
"type" : "event" ,
"event" : {
"event_type" : "state_changed" ,
"data" : {
"entity_id" : "light.living_room" ,
"new_state" : { ... },
"old_state" : { ... }
},
"origin" : "LOCAL" ,
"time_fired" : "2026-03-10T12:00:00.000000+00:00"
}
}
Unsubscribe from Events
{
"id" : 4 ,
"type" : "unsubscribe_events" ,
"subscription" : 2
}
Get States
Get all entity states:
{
"id" : 5 ,
"type" : "get_states"
}
Response:
{
"id" : 5 ,
"type" : "result" ,
"success" : true ,
"result" : [
{
"entity_id" : "light.living_room" ,
"state" : "on" ,
"attributes" : {
"brightness" : 255
},
"last_changed" : "2026-03-10T12:00:00.000000+00:00" ,
"last_updated" : "2026-03-10T12:00:00.000000+00:00"
}
]
}
Get Config
Retrieve Home Assistant configuration:
{
"id" : 6 ,
"type" : "get_config"
}
Call Service
Execute a service:
{
"id" : 7 ,
"type" : "call_service" ,
"domain" : "light" ,
"service" : "turn_on" ,
"service_data" : {
"entity_id" : "light.living_room" ,
"brightness" : 200
}
}
With response data (Home Assistant 2024.4+):
{
"id" : 8 ,
"type" : "call_service" ,
"domain" : "conversation" ,
"service" : "process" ,
"service_data" : {
"text" : "Turn on the lights"
},
"return_response" : true
}
Response:
{
"id" : 8 ,
"type" : "result" ,
"success" : true ,
"result" : {
"response" : {
"speech" : "Turned on the lights"
}
}
}
Ping/Pong
Keep connection alive:
{
"id" : 9 ,
"type" : "ping"
}
Response:
{
"id" : 9 ,
"type" : "pong"
}
Registering Custom Commands
Integrations can register custom WebSocket commands using the @websocket_command decorator:
from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback
import voluptuous as vol
@websocket_api.websocket_command (
{
vol.Required( "type" ): "my_integration/custom_command" ,
vol.Required( "param1" ): str ,
vol.Optional( "param2" , default = 10 ): int ,
}
)
@callback
def handle_custom_command (
hass : HomeAssistant,
connection : websocket_api.ActiveConnection,
msg : dict ,
) -> None :
"""Handle custom command."""
result = {
"processed" : msg[ "param1" ],
"value" : msg[ "param2" ] * 2 ,
}
connection.send_result(msg[ "id" ], result)
Register during integration setup:
async def async_setup ( hass : HomeAssistant, config : dict ) -> bool :
"""Set up the integration."""
websocket_api.async_register_command(hass, handle_custom_command)
return True
Async Response Commands
For async operations, use the @async_response decorator:
@websocket_api.websocket_command (
{
vol.Required( "type" ): "my_integration/async_command" ,
vol.Required( "device_id" ): str ,
}
)
@websocket_api.async_response
async def handle_async_command (
hass : HomeAssistant,
connection : websocket_api.ActiveConnection,
msg : dict ,
) -> None :
"""Handle async command."""
# Perform async operations
data = await fetch_device_data(msg[ "device_id" ])
connection.send_result(msg[ "id" ], data)
Permission Requirements
Require admin permissions:
@websocket_api.require_admin
@websocket_api.websocket_command ({ ... })
def admin_only_command ( hass , connection , msg ):
"""Admin-only command."""
...
Current Connection
Access the current WebSocket connection in async code:
from homeassistant.components.websocket_api import current_connection
@callback
def my_callback ():
"""Send message to current connection."""
connection = current_connection.get()
if connection:
connection.send_message(
websocket_api.event_message(
subscription_id,
{ "custom_event" : "data" },
)
)
Message Helpers
From homeassistant/components/websocket_api/messages.py:
from homeassistant.components.websocket_api import (
result_message,
error_message,
event_message,
)
# Send success result
connection.send_message(result_message(msg_id, { "data" : "value" }))
# Send error
connection.send_message(
error_message(msg_id, "not_found" , "Entity not found" )
)
# Send event
connection.send_message(
event_message(subscription_id, { "event" : "data" })
)
Connection Management
The ActiveConnection class manages WebSocket connections:
class ActiveConnection :
"""Represents an active WebSocket connection."""
def send_message ( self , message : dict ) -> None :
"""Send a message to the client."""
def send_result ( self , msg_id : int , result : Any) -> None :
"""Send a successful result."""
def send_error ( self , msg_id : int , code : str , message : str ) -> None :
"""Send an error result."""
Best Practices
Handle Reconnection Implement automatic reconnection with exponential backoff
Validate Messages Always validate command parameters with voluptuous schemas
Use Callbacks Use @callback for sync handlers to avoid blocking the event loop
Clean Up Subscriptions Unsubscribe from events when no longer needed
Full Example
class HomeAssistantClient {
constructor ( url , token ) {
this . url = url ;
this . token = token ;
this . messageId = 1 ;
this . pendingMessages = new Map ();
}
connect () {
this . ws = new WebSocket ( this . url );
this . ws . onopen = () => {
console . log ( 'Connected' );
};
this . ws . onmessage = ( event ) => {
const message = JSON . parse ( event . data );
if ( message . type === 'auth_required' ) {
this . authenticate ();
} else if ( message . type === 'auth_ok' ) {
console . log ( 'Authenticated' );
this . onReady ();
} else if ( message . type === 'result' ) {
this . handleResult ( message );
} else if ( message . type === 'event' ) {
this . handleEvent ( message );
}
};
}
authenticate () {
this . ws . send ( JSON . stringify ({
type: 'auth' ,
access_token: this . token
}));
}
sendCommand ( type , data = {}) {
const id = this . messageId ++ ;
const message = { id , type , ... data };
return new Promise (( resolve , reject ) => {
this . pendingMessages . set ( id , { resolve , reject });
this . ws . send ( JSON . stringify ( message ));
});
}
handleResult ( message ) {
const pending = this . pendingMessages . get ( message . id );
if ( pending ) {
if ( message . success ) {
pending . resolve ( message . result );
} else {
pending . reject ( message . error );
}
this . pendingMessages . delete ( message . id );
}
}
async getStates () {
return await this . sendCommand ( 'get_states' );
}
async callService ( domain , service , serviceData ) {
return await this . sendCommand ( 'call_service' , {
domain ,
service ,
service_data: serviceData
});
}
async subscribeEvents ( eventType ) {
return await this . sendCommand ( 'subscribe_events' , {
event_type: eventType
});
}
handleEvent ( message ) {
console . log ( 'Event:' , message . event );
}
onReady () {
// Called when authenticated and ready
}
}
// Usage
const client = new HomeAssistantClient (
'ws://localhost:8123/api/websocket' ,
'YOUR_TOKEN'
);
client . connect ();
See Also
REST API HTTP-based API for polling operations
Event System Understanding Home Assistant events
Service Calls Learn about calling services
Authentication Authentication and permissions system