Skip to content

Commit 99aaef0

Browse files
author
techartdev
committed
Add HTTPS/SSL support and enhance connection handling
- Implemented HTTPS/SSL support for OpenClaw gateways in `lan_https` mode. - Added configuration options for SSL certificate verification. - Improved auto-discovery for `lan_https` access mode. - Updated documentation in README and changelog for new features and fixes. - Fixed connection errors related to SSL certificate verification.
1 parent 8f82526 commit 99aaef0

10 files changed

Lines changed: 150 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
44

5+
## [0.1.52] - 2026-02-23
6+
7+
### Added
8+
- Added HTTPS / SSL support for connecting to OpenClaw gateways running in `lan_https` mode or behind TLS reverse proxies.
9+
- Auto-discovery now detects `access_mode: lan_https` and connects to the internal gateway port automatically (no certificate setup needed for local addons).
10+
- Added `Verify SSL certificate` option in manual config for self-signed certificate environments.
11+
- Added `ssl_error` config flow error with actionable guidance.
12+
- Added comprehensive remote connection documentation to README with setup table for all access modes.
13+
14+
### Fixed
15+
- Fixed "400 Bad Request — plain HTTP request was sent to HTTPS port" when the addon uses `lan_https` access mode.
16+
517
## [0.1.51] - 2026-02-23
618

719
### Fixed

README.md

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,68 @@ OpenClaw is a Home Assistant custom integration that connects your HA instance t
3838
## Requirements
3939

4040
- Home Assistant Core `2025.1.0+` (declared minimum)
41-
- Supervisor is optional (used for auto-discovery)
42-
- OpenClaw Assistant addon installed and running
41+
- An **OpenClaw gateway** with `enable_openai_api` enabled — either:
42+
- The [OpenClaw Assistant addon](https://github.com/techartdev/OpenClawHomeAssistant) running on the same HA instance (auto-discovery supported), **or**
43+
- Any standalone [OpenClaw](https://github.com/openclaw/openclaw) installation reachable over the network (manual config)
44+
- Supervisor is optional (used only for addon auto-discovery)
4345

44-
The integration can auto-detect the addon when Supervisor is available. You can always configure host/port/token manually.
46+
> **No addon required.** If you have OpenClaw running anywhere — on a separate server, a VPS, a Docker container, or even another machine on your LAN — this integration can connect to it via the manual configuration flow.
47+
48+
---
49+
50+
## Connection modes
51+
52+
The integration supports connecting to OpenClaw in several ways:
53+
54+
### Local addon (auto-discovery)
55+
56+
If the OpenClaw Assistant addon is installed on the **same** Home Assistant instance, the integration auto-discovers it:
57+
- Reads token from the shared filesystem
58+
- Detects `access_mode` and chooses the correct port automatically
59+
- No manual config needed — just click **Submit** on the confirm step
60+
61+
> **`lan_https` mode**: The integration automatically connects to the internal gateway port (plain HTTP on loopback), bypassing the HTTPS proxy entirely. No certificate setup required.
62+
63+
### Remote or standalone OpenClaw instance (manual config)
64+
65+
You can connect to **any reachable OpenClaw gateway** — whether it's the HA addon on another machine, a standalone `openclaw` install on a VPS, or a Docker container on your LAN. The integration doesn't care how OpenClaw is installed; it only needs the `/v1/chat/completions` endpoint.
66+
67+
**Prerequisites on the OpenClaw instance:**
68+
69+
1. The OpenAI-compatible API must be **enabled**:
70+
- **Addon users**: Set `enable_openai_api: true` in addon settings
71+
- **Standalone users**: Set `gateway.http.endpoints.chatCompletions.enabled: true` in `openclaw.json`, or run:
72+
```sh
73+
openclaw config set gateway.http.endpoints.chatCompletions.enabled true
74+
```
75+
2. The gateway must be **network-reachable** from your HA instance (not bound to loopback only)
76+
3. You need the **gateway auth token**:
77+
```sh
78+
openclaw config get gateway.auth.token
79+
```
80+
81+
**Setup steps:**
82+
83+
1. Go to **Settings → Devices & Services → Add Integration → OpenClaw**
84+
2. Auto-discovery will fail (no local addon) — you'll see the **Manual Configuration** form
85+
3. Fill in:
86+
- **Gateway Host**: IP or hostname of the remote machine (e.g. `192.168.1.50`)
87+
- **Gateway Port**: The gateway port (default `18789`)
88+
- **Gateway Token**: Auth token from the remote `openclaw.json`
89+
- **Use SSL (HTTPS)**: Check if connecting to an HTTPS endpoint
90+
- **Verify SSL certificate**: Uncheck for self-signed certificates (e.g. `lan_https` mode)
91+
92+
### Common remote scenarios
93+
94+
| Remote access mode | Host | Port | Use SSL | Verify SSL | Notes |
95+
|---|---|---|---|---|---|
96+
| Standalone OpenClaw (plain HTTP on LAN) | Remote IP | 18789 | ❌ | — | Default `openclaw gateway run` config |
97+
| `lan_https` (addon built-in HTTPS proxy) | Remote IP | 18789 | ✅ | ❌ | Self-signed cert; disable verification |
98+
| Behind reverse proxy (NPM/Caddy with Let's Encrypt) | Domain or IP | 443 ||| Trusted cert from a real CA |
99+
| Plain HTTP addon on LAN | Remote IP | 18789 ||| Addon `bind_mode` must be `lan` |
100+
| Tailscale | Tailscale IP | 18789 ||| Encrypted tunnel; plain HTTP is fine |
101+
102+
> **Security note**: Avoid exposing plain HTTP gateways to the public internet. Use `lan_https`, a reverse proxy with TLS, or Tailscale for remote access.
45103

46104
---
47105

@@ -307,6 +365,12 @@ action:
307365
- Verify `openclaw_message_received` is being fired in Developer Tools → Events
308366
- Confirm session IDs match between card and service calls
309367

368+
### "400 Bad Request — plain HTTP request was sent to HTTPS port"
369+
370+
- The gateway is running in `lan_https` mode (built-in HTTPS proxy)
371+
- **Local addon**: Remove and re-add the integration — auto-discovery now detects `lan_https` and uses the correct internal port automatically
372+
- **Remote connection**: Enable **Use SSL (HTTPS)** and disable **Verify SSL certificate** in the manual config
373+
310374
---
311375

312376
## Development notes

custom_components/openclaw/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
CONF_GATEWAY_PORT,
5252
CONF_GATEWAY_TOKEN,
5353
CONF_USE_SSL,
54+
CONF_VERIFY_SSL,
5455
CONF_CONTEXT_MAX_CHARS,
5556
CONF_CONTEXT_STRATEGY,
5657
CONF_ENABLE_TOOL_CALLS,
@@ -92,7 +93,7 @@
9293
# URL at which the card JS is served (registered via register_static_path)
9394
_CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}"
9495
# Versioned URL used for Lovelace resource registration to avoid stale browser cache
95-
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.51"
96+
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.52"
9697

9798
OpenClawConfigEntry = ConfigEntry
9899

@@ -130,13 +131,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenClawConfigEntry) ->
130131
131132
Creates the API client, coordinator, and forwards setup to platforms.
132133
"""
133-
session = async_get_clientsession(hass)
134+
use_ssl = entry.data.get(CONF_USE_SSL, False)
135+
verify_ssl = entry.data.get(CONF_VERIFY_SSL, True)
136+
session = async_get_clientsession(hass, verify_ssl=verify_ssl)
134137

135138
client = OpenClawApiClient(
136139
host=entry.data[CONF_GATEWAY_HOST],
137140
port=entry.data[CONF_GATEWAY_PORT],
138141
token=entry.data[CONF_GATEWAY_TOKEN],
139-
use_ssl=entry.data.get(CONF_USE_SSL, False),
142+
use_ssl=use_ssl,
143+
verify_ssl=verify_ssl,
140144
session=session,
141145
)
142146

custom_components/openclaw/api.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def __init__(
4949
port: int,
5050
token: str,
5151
use_ssl: bool = False,
52+
verify_ssl: bool = True,
5253
session: aiohttp.ClientSession | None = None,
5354
) -> None:
5455
"""Initialize the API client.
@@ -58,14 +59,19 @@ def __init__(
5859
port: Gateway port number.
5960
token: Authentication token from openclaw.json.
6061
use_ssl: Use HTTPS instead of HTTP.
62+
verify_ssl: Verify SSL certificates (set False for self-signed certs).
6163
session: Optional aiohttp session (reused from HA).
6264
"""
6365
self._host = host
6466
self._port = port
6567
self._token = token
6668
self._use_ssl = use_ssl
69+
self._verify_ssl = verify_ssl
6770
self._session = session
6871
self._base_url = f"{'https' if use_ssl else 'http'}://{host}:{port}"
72+
# ssl=False disables cert verification for self-signed certs;
73+
# ssl=None uses default verification.
74+
self._ssl_param: bool | None = False if (use_ssl and not verify_ssl) else None
6975

7076
@property
7177
def base_url(self) -> str:
@@ -121,6 +127,7 @@ async def _request(
121127
url,
122128
headers=self._headers(),
123129
timeout=timeout,
130+
ssl=self._ssl_param,
124131
**kwargs,
125132
) as resp:
126133
if resp.status == 401:
@@ -146,6 +153,13 @@ async def _request(
146153
)
147154
return await resp.json()
148155

156+
except aiohttp.ClientConnectorCertificateError as err:
157+
raise OpenClawConnectionError(
158+
f"SSL certificate verification failed for {url}. "
159+
f"If using self-signed certificates (e.g. lan_https mode), "
160+
f"disable 'Verify SSL certificate' in the integration config. "
161+
f"Error: {err}"
162+
) from err
149163
except (aiohttp.ClientConnectorError, aiohttp.ClientOSError, asyncio.TimeoutError) as err:
150164
raise OpenClawConnectionError(
151165
f"Cannot connect to OpenClaw gateway at {url}: {err}"
@@ -213,6 +227,7 @@ async def async_send_message(
213227
headers=headers,
214228
json=payload,
215229
timeout=STREAM_TIMEOUT,
230+
ssl=self._ssl_param,
216231
) as resp:
217232
if resp.status == 401:
218233
raise OpenClawAuthError("Authentication failed")
@@ -274,6 +289,7 @@ async def async_stream_message(
274289
headers=headers,
275290
json=payload,
276291
timeout=STREAM_TIMEOUT,
292+
ssl=self._ssl_param,
277293
) as resp:
278294
if resp.status == 401:
279295
raise OpenClawAuthError("Authentication failed")
@@ -338,6 +354,7 @@ async def async_check_connection(self) -> bool:
338354
headers=self._headers(),
339355
json={"messages": [], "stream": False},
340356
timeout=API_TIMEOUT,
357+
ssl=self._ssl_param,
341358
) as resp:
342359
if resp.status in (401, 403):
343360
raise OpenClawAuthError(
@@ -381,6 +398,7 @@ async def async_check_alive(self) -> bool:
381398
async with session.get(
382399
self._base_url,
383400
timeout=API_TIMEOUT,
401+
ssl=self._ssl_param,
384402
) as resp:
385403
return resp.status < 500
386404
except (aiohttp.ClientConnectorError, aiohttp.ClientOSError, asyncio.TimeoutError) as err:
@@ -424,6 +442,7 @@ async def async_invoke_tool(
424442
headers=headers,
425443
json=payload,
426444
timeout=STREAM_TIMEOUT,
445+
ssl=self._ssl_param,
427446
) as resp:
428447
if resp.status == 401:
429448
raise OpenClawAuthError("Authentication failed")

custom_components/openclaw/config_flow.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
CONF_GATEWAY_PORT,
3939
CONF_GATEWAY_TOKEN,
4040
CONF_USE_SSL,
41+
CONF_VERIFY_SSL,
4142
CONF_CONTEXT_MAX_CHARS,
4243
CONF_CONTEXT_STRATEGY,
4344
CONF_ENABLE_TOOL_CALLS,
@@ -233,11 +234,30 @@ async def _async_try_discover_addon(hass: HomeAssistant) -> dict[str, Any] | Non
233234
if port is None:
234235
port = DEFAULT_GATEWAY_PORT
235236

237+
# Detect access mode — lan_https uses a built-in HTTPS reverse proxy.
238+
# In that mode nginx terminates TLS on the external (user-facing) port
239+
# and the gateway itself listens on port+1 on loopback (plain HTTP).
240+
# Since HA and the addon share host_network, we connect to the internal
241+
# port via loopback to avoid self-signed certificate issues entirely.
242+
access_mode = addon_options.get("access_mode", "custom")
243+
use_ssl = False
244+
verify_ssl = True
245+
246+
if access_mode == "lan_https":
247+
_LOGGER.info(
248+
"Detected access_mode=lan_https — using internal gateway port %d "
249+
"(external HTTPS proxy is on port %d)",
250+
port + 1,
251+
port,
252+
)
253+
port = port + 1
254+
236255
return {
237256
CONF_GATEWAY_HOST: DEFAULT_GATEWAY_HOST,
238257
CONF_GATEWAY_PORT: port,
239258
CONF_GATEWAY_TOKEN: token,
240-
CONF_USE_SSL: False,
259+
CONF_USE_SSL: use_ssl,
260+
CONF_VERIFY_SSL: verify_ssl,
241261
CONF_ADDON_CONFIG_PATH: str(config_dir),
242262
}
243263

@@ -248,14 +268,16 @@ async def _async_validate_connection(
248268
port: int,
249269
token: str,
250270
use_ssl: bool = False,
271+
verify_ssl: bool = True,
251272
) -> bool:
252273
"""Validate that we can connect and authenticate to the gateway."""
253-
session = async_get_clientsession(hass)
274+
session = async_get_clientsession(hass, verify_ssl=verify_ssl)
254275
client = OpenClawApiClient(
255276
host=host,
256277
port=port,
257278
token=token,
258279
use_ssl=use_ssl,
280+
verify_ssl=verify_ssl,
259281
session=session,
260282
)
261283
return await client.async_check_connection()
@@ -312,6 +334,7 @@ async def async_step_confirm(
312334
self._discovered[CONF_GATEWAY_PORT],
313335
self._discovered[CONF_GATEWAY_TOKEN],
314336
self._discovered.get(CONF_USE_SSL, False),
337+
self._discovered.get(CONF_VERIFY_SSL, True),
315338
)
316339
except OpenClawAuthError:
317340
connected = False
@@ -355,17 +378,23 @@ async def async_step_manual(
355378
port = user_input[CONF_GATEWAY_PORT]
356379
token = user_input[CONF_GATEWAY_TOKEN]
357380
use_ssl = user_input.get(CONF_USE_SSL, False)
381+
verify_ssl = user_input.get(CONF_VERIFY_SSL, True)
358382

359383
try:
360384
connected = await _async_validate_connection(
361-
self.hass, host, port, token, use_ssl
385+
self.hass, host, port, token, use_ssl, verify_ssl
362386
)
363387
except OpenClawAuthError:
364388
errors["base"] = "invalid_auth"
365389
connected = False
366-
except OpenClawConnectionError:
367-
errors["base"] = "cannot_connect"
390+
except OpenClawConnectionError as err:
391+
err_msg = str(err).lower()
392+
if "ssl" in err_msg or "certificate" in err_msg:
393+
errors["base"] = "ssl_error"
394+
else:
395+
errors["base"] = "cannot_connect"
368396
connected = False
397+
_LOGGER.warning("Connection error during config check: %s", err)
369398
except OpenClawApiError as err:
370399
errors["base"] = "openai_api_disabled"
371400
connected = False
@@ -379,6 +408,7 @@ async def async_step_manual(
379408
CONF_GATEWAY_PORT: port,
380409
CONF_GATEWAY_TOKEN: token,
381410
CONF_USE_SSL: use_ssl,
411+
CONF_VERIFY_SSL: verify_ssl,
382412
},
383413
)
384414
if "base" not in errors:
@@ -396,6 +426,7 @@ async def async_step_manual(
396426
): vol.All(int, vol.Range(min=1, max=65535)),
397427
vol.Required(CONF_GATEWAY_TOKEN): str,
398428
vol.Optional(CONF_USE_SSL, default=False): bool,
429+
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
399430
}
400431
),
401432
errors=errors,

custom_components/openclaw/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
CONF_GATEWAY_PORT = "gateway_port"
2222
CONF_GATEWAY_TOKEN = "gateway_token"
2323
CONF_USE_SSL = "use_ssl"
24+
CONF_VERIFY_SSL = "verify_ssl"
2425
CONF_ADDON_CONFIG_PATH = "addon_config_path"
2526

2627
# Options

custom_components/openclaw/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"iot_class": "local_polling",
99
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
1010
"requirements": [],
11-
"version": "0.1.51",
11+
"version": "0.1.52",
1212
"dependencies": ["conversation"],
1313
"after_dependencies": ["hassio", "lovelace"]
1414
}

custom_components/openclaw/strings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
"gateway_host": "Gateway Host",
1818
"gateway_port": "Gateway Port",
1919
"gateway_token": "Gateway Token",
20-
"use_ssl": "Use SSL (HTTPS)"
20+
"use_ssl": "Use SSL (HTTPS)",
21+
"verify_ssl": "Verify SSL certificate"
2122
}
2223
}
2324
},
2425
"error": {
2526
"cannot_connect": "Cannot connect to the OpenClaw gateway. Ensure the addon is running.",
26-
"invalid_auth": "Invalid gateway token. Check your OpenClaw configuration.",
27-
"openai_api_disabled": "The gateway returned an unexpected response — the OpenAI-compatible API is likely disabled. In the OpenClaw addon settings enable 'enable_openai_api', restart the addon, and try again.",
27+
"invalid_auth": "Invalid gateway token. Check your OpenClaw configuration.", "ssl_error": "SSL certificate verification failed. If using self-signed certificates (e.g. lan_https mode), uncheck 'Verify SSL certificate' or use automatic discovery.", "openai_api_disabled": "The gateway returned an unexpected response — the OpenAI-compatible API is likely disabled. In the OpenClaw addon settings enable 'enable_openai_api', restart the addon, and try again.",
2828
"unknown": "An unexpected error occurred."
2929
},
3030
"abort": {

custom_components/openclaw/translations/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
"gateway_host": "Gateway Host",
1818
"gateway_port": "Gateway Port",
1919
"gateway_token": "Gateway Token",
20-
"use_ssl": "Use SSL (HTTPS)"
20+
"use_ssl": "Use SSL (HTTPS)",
21+
"verify_ssl": "Verify SSL certificate"
2122
}
2223
}
2324
},
2425
"error": {
2526
"cannot_connect": "Cannot connect to the OpenClaw gateway. Ensure the addon is running.",
2627
"invalid_auth": "Invalid gateway token. Check your OpenClaw configuration.",
28+
"ssl_error": "SSL certificate verification failed. If using self-signed certificates (e.g. lan_https mode), uncheck 'Verify SSL certificate' or use automatic discovery.",
2729
"openai_api_disabled": "The gateway returned an unexpected response — the OpenAI-compatible API is likely disabled. In the OpenClaw addon settings enable 'enable_openai_api', restart the addon, and try again.",
2830
"unknown": "An unexpected error occurred."
2931
},

www/openclaw-chat-card.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(async () => {
22
try {
33
if (!customElements.get("openclaw-chat-card")) {
4-
const src = "/openclaw/openclaw-chat-card.js?v=0.1.51";
4+
const src = "/openclaw/openclaw-chat-card.js?v=0.1.52";
55
console.info("OpenClaw loader importing", src);
66
await import(src);
77
}

0 commit comments

Comments
 (0)