Skip to content

Custom Lovelace card cannot authenticate to add-on Ingress (503 + "Ingress not available") #7096

Description

@broomwalker

Describe the issue you are experiencing

HomePod Push-to-Talk Intercom — Project Summary

Goal

Build a push-to-talk intercom system on Home Assistant OS (HAOS) that broadcasts live voice from a phone to one or more Apple HomePods around the house, similar to Apple's own Home Intercom feature. Requirements:

  • Press-and-hold (or tap-to-start/tap-to-stop) recording on a phone
  • Broadcast to multiple HomePods simultaneously (Kitchen, Bedroom, Study)
  • Usable both at home and away from home
  • Ideally embedded as a native-feeling card on the main Home Assistant dashboard, not just a separate panel
  • Self-contained on HAOS (no reliance on a separate Unraid/Docker host)

What's Working Today

A complete, functioning system has been built and tested end-to-end:

  1. Icecast Intercom — a local HAOS app (custom-built, not a pre-made add-on) running Icecast, used during early development for live-streaming experiments.
  2. Intercom Talk — a local HAOS app (Node.js/Express) that:
    • Serves a "Hold to Talk" webpage (accessed via Home Assistant's sidebar panel, using Ingress)
    • Captures microphone audio in the browser, converts it to a WAV file via ffmpeg
    • Triggers a Home Assistant webhook once the recording is ready
  3. Music Assistant — installed as the audio delivery mechanism to the HomePods (via its AirPlay provider), after Home Assistant's built-in Apple TV integration proved unable to play the recorded files (see below).
  4. A Home Assistant automation ("Intercom Message") — triggered by the webhook, calls media_player.play_media targeting the Music Assistant HomePod entities (media_player.kitchen_2, media_player.bedroom, media_player.study_2) with the recorded file's URL.

Confirmed working: press the Talk button on the sidebar panel, speak, release, and the message plays through the HomePod(s) shortly after.

Not yet done: multi-HomePod broadcast hasn't been re-tested since the automation was updated to target all three rooms (only single-room Kitchen playback has been confirmed); remote (away-from-home) access has not yet been secured/tested.

What We Tried (in rough chronological order)

  1. Live real-time streaming to Icecast, triggered on button press, with HomePods pre-connected to a permanently-open "silence" stream so voice could seamlessly replace it. This solved the fundamental timing conflict between Icecast (which only serves a mountpoint while actively receiving a source) and AirPlay (which takes several seconds to negotiate a connection).
  2. Home Assistant's built-in Apple TV integration (pyatv) to send the stream/file to the HomePods. This consistently failed with miniaudio.DecodeError: ('failed to init decoder', ...) — confirmed via Home Assistant's own core logs to be a failure inside pyatv's bundled miniaudio library, reproducible even with a completely unrelated, valid, well-known public WAV file (i.e., not caused by anything in our own file/format/server). This appears to be a genuine bug in this version of pyatv/the Apple TV integration.
  3. Switched delivery to Music Assistant (its own add-on + integration, using its AirPlay provider) instead of the Apple TV integration. This worked immediately — the failure was isolated to pyatv, not to Home Assistant's media_player.play_media action in general.
  4. A custom Lovelace dashboard card (intercom-test-card), to move the Talk button from a separate sidebar panel into the actual dashboard grid, with tap-to-start/tap-to-stop interaction. This required the card's JavaScript to:
    • Obtain a valid Home Assistant Ingress session (needed because the add-on's web server is only reachable through Ingress, not a directly exposed port, to avoid exposing anything to the internet)
    • Use that session to open a connection (first via WebSocket for live streaming, later via a plain HTTPS file upload for a simpler tap-to-record-then-send flow) directly to the Intercom Talk add-on
    • Request microphone access from within the dashboard page

Problems Encountered

Resolved

  • Home Assistant Supervisor's init: false requirement for s6-overlay-based container images (an early s6-overlay-suexec: fatal: can only run as pid 1 crash).
  • bashio/s6-overlay dependency was itself the source of repeated container crashes; switched to plain sh + jq for reading add-on config.
  • Icecast refusing to run as root; fixed via a <changeowner> block in icecast.xml, inserted via a single-line sed command (a multi-line sed insertion using \n had silently failed due to BusyBox sed's limited support for embedded newlines in the replacement string).
  • HA Supervisor's local app store not picking up file changes without an explicit ha store reload and a version-number bump — editing files in place was not sufficient to trigger a rebuild.
  • Ingress URL construction bugs (fetch('/talk') vs fetch('talk'), location.origin vs window.location.href) that broke requests specifically when the page was loaded through Home Assistant's Ingress proxy (which serves pages from a sub-path, not the domain root).

Unresolved — the core open problem

A custom Lovelace card cannot successfully authenticate to a Home Assistant Ingress-proxied add-on, despite the add-on's own sidebar panel (which also goes through Ingress) working perfectly.

What we found:

  • The publicly-known REST endpoint (hass.callApi('POST', 'hassio/ingress/session')) returns 401 Unauthorized when called from custom card JavaScript.
  • Calling the same underlying operation via the WebSocket API instead (hass.callWS({ type: 'supervisor/api', endpoint: '/ingress/session', method: 'post' })) does succeed and returns a session token.
  • However, using that token — via document.cookie in the same way Home Assistant's own frontend source code does it (ingress_session=<token>; path=/api/hassio_ingress/; SameSite=Strict; Secure) — does not result in a working connection. Every subsequent request to the add-on via /api/hassio_ingress/<slug>/, regardless of method (WebSocket, plain GET, POST), fails with a clean 503 Service Unavailable from Home Assistant's own proxy, and Home Assistant Supervisor's log shows [supervisor.api.ingress] Ingress for local_intercom_talk not available at the same moment — even though the add-on itself is confirmed running and healthy, and is simultaneously reachable and working correctly through its normal sidebar panel.

This was isolated with a minimal test card that does nothing but request a session and issue one plain GET request — ruling out anything specific to WebSockets, file uploads, or our own app's code as the cause.

Questions for Home Assistant / Supervisor Developers

  1. Is there a supported, documented way for a custom Lovelace card (not a built-in panel) to open a valid Ingress session to a local add-on from within its own JavaScript? The only working example we could find of Ingress session creation is in Home Assistant's own frontend source (src/data/hassio/ingress.ts), which appears to call a REST endpoint that returns 401 when called the same way from outside the core frontend bundle.
  2. Does the session token returned by hass.callWS({ type: 'supervisor/api', endpoint: '/ingress/session', method: 'post' }) differ in any meaningful way (scope, associated user/session context, expiry) from the one generated internally when the frontend navigates to a registered Ingress panel? Our evidence suggests it does, since the resulting cookie is accepted for session creation but not for subsequent routing to the add-on.
  3. Is [supervisor.api.ingress] Ingress for local_intercom_talk not available logged only when the target add-on is unreachable, or can it also indicate a session/token mismatch or invalid-session condition specifically? The wording suggests a connectivity problem, but the add-on was confirmed running and reachable via its normal panel at the same time.
  4. Is there a supported way to register a "webpage"/custom element that runs with the same privileges as a built-in Ingress panel, without going through the full panel registration and navigation flow (i.e., embedded inline in a dashboard rather than as its own full-page panel)?

What type of installation are you running?

Home Assistant OS

Which operating system are you running on?

Home Assistant Operating System

Steps to reproduce the issue

  1. Create a local add-on with ingress: true enabled (no direct ports mapping required to reproduce — tested both with and without one alongside Ingress).

  2. Confirm the add-on works correctly through its own sidebar panel (Settings → Apps → [addon] → Open Web UI, or tapping it in the sidebar if panel_title/panel_icon are set). This confirms Ingress routing to the add-on works under normal conditions.

  3. Create a custom Lovelace card (or any script running in the main dashboard frontend context, separate from the add-on's own Ingress-served page) and, from its JavaScript, request an Ingress session:

    const session = await hass.callWS({
    type: 'supervisor/api',
    endpoint: '/ingress/session',
    method: 'post'
    });

    This call succeeds and returns a session token (e.g. {"session": "<64-char hex token>"}).

  4. Set the returned token as a cookie, matching the pattern used in Home Assistant's own frontend source (src/data/hassio/ingress.ts):

    document.cookie = ingress_session=${session.session}; path=/api/hassio_ingress/; SameSite=Strict; Secure;

  5. From the same card/script, make any request to the add-on via its Ingress path, e.g.:

    const res = await fetch(
    ${location.protocol}//${location.host}/api/hassio_ingress/<addon_slug>/,
    { method: 'GET', credentials: 'include' }
    );

Expected result: a 200 response from the add-on, the same as when accessed via its own sidebar panel.

Actual result: a clean 503 Service Unavailable response, every time, regardless of HTTP method (tested with GET, POST with file upload, and WebSocket connection attempts — all fail identically). Home Assistant Supervisor's log shows, at the same moment:

WARNING (MainThread) [supervisor.api.ingress] Ingress for <addon_slug> not available

...despite the add-on being confirmed running (state: started) and simultaneously reachable via its own sidebar panel in the same session.

This was isolated with a minimal test card that does nothing but request a session and issue one plain GET request, to rule out anything specific to WebSockets or file uploads.


Context: the underlying goal was to embed an add-on's own web page/functionality (a microphone-capture push-to-talk interface) directly inside a custom dashboard card, rather than as a separate sidebar panel — so users interact with it inline among their other dashboard tiles. The sidebar-panel approach already works perfectly and was used as the working baseline for comparison above. If there's a supported, documented way to grant a custom card the same Ingress access a registered panel gets, that would also resolve this — we weren't able to find one, but may have missed it.

Anything in the Supervisor logs that might be useful for us?

# Supervisor logs showing repeated Ingress failures during custom card testing,
# captured at the same moments each failed request was made from the card.
# The add-on (local_intercom_talk) was confirmed `state: started` throughout.

[supervisor.api.ingress] No valid ingress session
[supervisor.api.ingress] Ingress for local_intercom_talk not available
[supervisor.api.ingress] Ingress for local_intercom_talk not available
[supervisor.api.ingress] Ingress for local_intercom_talk not available

# The following appeared once, briefly, right after a rebuild/restart of the
# add-on's container — possibly unrelated, but included in case it's connected:

[supervisor.docker.manager] Could not get stats for addon_local_intercom_talk
[supervisor.apps.app] Could not get stats of container for app local_intercom_talk: Could not get stats for addon_local_intercom_talk

System information

System Information

version core-2026.7.4
installation_type Home Assistant OS
dev false
hassio true
docker true
container_arch amd64
user root
virtualenv false
python_version 3.14.6
os_name Linux
os_version 6.18.37-haos
arch x86_64
timezone Australia/Sydney
config_dir /config
Home Assistant Cloud
logged_in false
can_reach_cert_server ok
can_reach_cloud_auth ok
can_reach_cloud ok
HACS
GitHub API ok
GitHub Content ok
GitHub Web ok
HACS Data ok
GitHub API Calls Remaining 5000
Installed Version 2.0.5
Stage running
Available Repositories 3455
Downloaded Repositories 22
Home Assistant Supervisor
host_os Home Assistant OS 18.1
update_channel stable
supervisor_version supervisor-2026.07.3
agent_version 1.9.0
docker_version 29.5.3
disk_total 468.7 GB
disk_used 18.0 GB
nameservers 192.168.1.60, 192.168.1.20
healthy true
supported true
host_connectivity true
supervisor_connectivity true
ntp_synchronized true
virtualization
board generic-x86-64
disk_life_time 0 %
supervisor_api ok
version_api ok
installed_addons Matter Server (9.1.0), File editor (6.0.0), Terminal & SSH (10.3.0), OpenThread Border Router (3.0.2), Grafana (12.1.0), Icecast Intercom (1.0.6), Music Assistant (2.9.9), Intercom Talk (1.0.11)
Dashboards
dashboards 13
resources 10
views 11
mode storage
Network Configuration
adapters lo (disabled), eno1 (enabled, default, auto), docker0 (disabled), hassio (disabled), veth503784e (disabled), veth35f8854 (disabled), veth3badcb3 (disabled), veth54e344a (disabled), veth67a2591 (disabled), veth11c16a6 (disabled), wpan0 (disabled), vethae82af9 (disabled), veth66f94c1 (disabled)
ipv4_addresses lo (127.0.0.1/8), eno1 (192.168.1.15/24), docker0 (172.30.232.1/23), hassio (172.30.32.1/23), veth503784e (), veth35f8854 (), veth3badcb3 (), veth54e344a (), veth67a2591 (), veth11c16a6 (), wpan0 (), vethae82af9 (), veth66f94c1 ()
ipv6_addresses lo (::1/128), eno1 (fd5f:3019:ed21:6e39:f567:5a57:1bd1:8cb1/64, fe80::3700:2b90:42a:8c6a/64), docker0 (fd9d:c50c:2aba::1/64, fe80::4c54:faff:fe40:d5ae/64), hassio (fd0c:ac1e:2100::1/48, fe80::6c77:c0ff:fed9:ecc0/64), veth503784e (fe80::60fd:1bff:fea2:4a37/64), veth35f8854 (fe80::d83f:9dff:fe34:9ce9/64), veth3badcb3 (fe80::f8c6:57ff:fe92:7607/64), veth54e344a (fe80::3cdf:1aff:fe75:a12a/64), veth67a2591 (fe80::84b5:cdff:fe4b:e11c/64), veth11c16a6 (fe80::8de:ffff:fe80:3496/64), wpan0 (fd16:cc4d:512d:3b66:0:ff:fe00:fc11/64, fdd0:4bdc:d219:1:f043:d4ac:fd3f:e73c/64, fd16:cc4d:512d:3b66:0:ff:fe00:fc38/64, fd16:cc4d:512d:3b66:0:ff:fe00:fc10/64, fd16:cc4d:512d:3b66:0:ff:fe00:fc00/64, fd16:cc4d:512d:3b66:0:ff:fe00:dc00/64, fd16:cc4d:512d:3b66:4950:407f:467a:8f2d/64, fe80::cea:9fc3:5a2d:9308/64), vethae82af9 (fe80::bc8b:8ff:fefc:2b38/64), veth66f94c1 (fe80::8454:97ff:fe5b:7b4b/64)
announce_addresses 192.168.1.15, fd5f:3019:ed21:6e39:f567:5a57:1bd1:8cb1, fe80::3700:2b90:42a:8c6a
Recorder
oldest_recorder_run July 14, 2026 at 8:49 AM
current_recorder_run July 28, 2026 at 12:51 PM
estimated_db_size 921.43 MiB
database_engine sqlite
database_version 3.53.2
WiiM
configured_devices 1
reachable_devices 1/1
multiroom_masters 0
multiroom_slaves 0
first_device_api OK (polling: 5.0s)
integration_version 2.0.0
pywiim_version 2.3.0

Supervisor diagnostics

No response

Additional information

System versions at time of testing (from attached diagnostics):

  • Home Assistant Core: 2026.7.4
  • Supervisor: 2026.07.3
  • Home Assistant OS: 18.1
  • Architecture: x86_64 / amd64

Client tested: Safari on iPadOS, both over home wifi and cellular (4G) — same 503 result in both cases, ruling out network path as a factor.

Note: testing via the native Home Assistant iOS app's built-in browser view produced a different, unrelated symptom (navigator.mediaDevices undefined entirely — a known limitation of that embedded browser context blocking microphone APIs). All results reported above are from genuine Safari, not the app's embedded view, to keep that separate issue out of this report.

Separately (not part of this Supervisor bug, but related context worth flagging): microphone access does not work at all inside the Home Assistant iOS Companion app's own in-app browser, even outside of Ingress/custom-card testing — this is due to WKWebView requiring the host app to implement Apple's media capture permission delegate, which the Companion app doesn't appear to do. This is already tracked separately on home-assistant/iOS (issues #1671 and #1319), so not re-reported here, but it means this feature currently only works via genuine Safari, not the Companion app, regardless of how the Ingress issue above is resolved.

Happy to provide the full custom card source, or set up a minimal reproduction repo, if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions