> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/home-assistant/core/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket API

> Real-time bidirectional communication API for Home Assistant

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:

```javascript theme={null}
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:

```json theme={null}
{
  "type": "auth_required",
  "ha_version": "2026.4.0"
}
```

2. Send an authentication message with your long-lived access token:

```json theme={null}
{
  "type": "auth",
  "access_token": "YOUR_LONG_LIVED_ACCESS_TOKEN"
}
```

3. If successful, you'll receive:

```json theme={null}
{
  "type": "auth_ok",
  "ha_version": "2026.4.0"
}
```

If authentication fails:

```json theme={null}
{
  "type": "auth_invalid",
  "message": "Invalid access token"
}
```

<Note>
  Create long-lived access tokens in the Home Assistant UI under your user profile.
</Note>

## Message Format

### Command Messages

All commands sent to Home Assistant follow this format:

```json theme={null}
{
  "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:**

```json theme={null}
{
  "id": 1,
  "type": "result",
  "success": true,
  "result": { ... }
}
```

**Error:**

```json theme={null}
{
  "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:

```json theme={null}
{
  "id": 2,
  "type": "subscribe_events",
  "event_type": "state_changed"
}
```

Subscribe to all events (omit `event_type`):

```json theme={null}
{
  "id": 3,
  "type": "subscribe_events"
}
```

**Event Messages:**

```json theme={null}
{
  "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

```json theme={null}
{
  "id": 4,
  "type": "unsubscribe_events",
  "subscription": 2
}
```

### Get States

Get all entity states:

```json theme={null}
{
  "id": 5,
  "type": "get_states"
}
```

**Response:**

```json theme={null}
{
  "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:

```json theme={null}
{
  "id": 6,
  "type": "get_config"
}
```

### Call Service

Execute a service:

```json theme={null}
{
  "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+):

```json theme={null}
{
  "id": 8,
  "type": "call_service",
  "domain": "conversation",
  "service": "process",
  "service_data": {
    "text": "Turn on the lights"
  },
  "return_response": true
}
```

**Response:**

```json theme={null}
{
  "id": 8,
  "type": "result",
  "success": true,
  "result": {
    "response": {
      "speech": "Turned on the lights"
    }
  }
}
```

### Ping/Pong

Keep connection alive:

```json theme={null}
{
  "id": 9,
  "type": "ping"
}
```

**Response:**

```json theme={null}
{
  "id": 9,
  "type": "pong"
}
```

## Registering Custom Commands

Integrations can register custom WebSocket commands using the `@websocket_command` decorator:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
@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:

```python theme={null}
@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:

```python theme={null}
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`:

```python theme={null}
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:

```python theme={null}
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

<CardGroup cols={2}>
  <Card title="Handle Reconnection" icon="rotate">
    Implement automatic reconnection with exponential backoff
  </Card>

  <Card title="Validate Messages" icon="shield-check">
    Always validate command parameters with voluptuous schemas
  </Card>

  <Card title="Use Callbacks" icon="bolt">
    Use @callback for sync handlers to avoid blocking the event loop
  </Card>

  <Card title="Clean Up Subscriptions" icon="broom">
    Unsubscribe from events when no longer needed
  </Card>
</CardGroup>

## Full Example

```javascript theme={null}
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

<CardGroup cols={2}>
  <Card title="REST API" icon="code" href="/api/rest-api">
    HTTP-based API for polling operations
  </Card>

  <Card title="Event System" icon="bell" href="/architecture/event-system">
    Understanding Home Assistant events
  </Card>

  <Card title="Service Calls" icon="phone" href="/architecture/service-calls">
    Learn about calling services
  </Card>

  <Card title="Authentication" icon="lock" href="/api/auth/overview">
    Authentication and permissions system
  </Card>
</CardGroup>
