Skip to main content
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

  1. Upon connection, you receive an auth_required message:
{
  "type": "auth_required",
  "ha_version": "2026.4.0"
}
  1. Send an authentication message with your long-lived access token:
{
  "type": "auth",
  "access_token": "YOUR_LONG_LIVED_ACCESS_TOKEN"
}
  1. 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.

Message Format

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

Build docs developers (and LLMs) love