User guide
August 29, 2026 ยท View on GitHub
This guide describes the normal VoIP Stack workflow from installation to the first local, SIP and trunk calls. Protocol and developer details are linked at the end.
Install Home Assistant VoIP Stack
- Open HACS and download VoIP Stack.
- Restart Home Assistant.
- Open Settings > Devices & services > Add integration.
- Select VoIP Stack.
- Keep SIP port
5060and RTP base port40000unless another service uses them. - Set Advertise host only when automatic detection does not produce an address reachable by every phone.
The first setup creates one ordinary Home Assistant browser phone. Its default
name comes from the Home Assistant location name, for example Casa. It is a
real logical phone, not a temporary setup object, and can receive calls even
before more phones are added.
Add the dashboard phone
- Add the VoIP Stack card to a dashboard.
- Select Home Assistant phone mode.
- Select the phone Device created during setup, for example
Casa. - Save the card.
- Grant microphone permission when the browser asks. Grant camera permission only when this phone must transmit video.
The card saves one public identity, the Home Assistant device_id. Incoming
calls for that phone move every connected card bound to the same Device to
ringing. The browser that answers becomes the media owner.
Example YAML:
type: custom:voip-stack-card
mode: ha_softphone
device_id: 0123456789abcdef0123456789abcdef
Use the card editor to select the Device. Do not copy a Device Registry ID from another installation.
What device_id and endpoint_id mean
For every public phone action:
device_id = the local phone performing the action
destination = the remote party to call
The same action works whether the local phone is a Home Assistant browser phone or a compatible ESPHome phone:
action: voip_stack.call
data:
device_id: 0123456789abcdef0123456789abcdef
destination: Waveshare S3 Audio
endpoint_id remains an internal stable identity used to correlate a logical
phone, SIP session, media owner and WebSocket snapshot. It is useful to the
backend because a Device can be temporarily unavailable while its logical
endpoint and active session still exist. Users do not save it in card YAML and
do not pass it to phone actions.
If device_id is omitted, VoIP Stack uses the explicitly preferred phone. If
no preferred phone is configured but exactly one compatible phone exists, that
phone is used. Otherwise the action asks for a phone selection instead of
guessing.
Choose the preferred phone from Settings > Devices & services > VoIP Stack >
Configure > Home Assistant phones. Removing or renaming the original Casa
phone is allowed after another preferred phone has been selected.
Add an ESPHome phone
- Start from a maintained profile under
yamls/. - Change only the documented substitutions, network secrets and hardware pins.
- Compile and flash the device.
- Add it through the normal ESPHome integration.
- Wait for VoIP Stack to discover its endpoint and publish it in the phonebook.
Normal custom profiles should include the semantic phone package:
packages:
voip_ha_phone: !include packages/voip/ha_phone.yaml
It provides the entities, phonebook subscription and native ESPHome actions required by Home Assistant:
start_call
answer_call
decline_call
hangup_call
An ESP mirror card selects the existing ESPHome Device. VoIP Stack does not create or own a duplicate Device.
Make local calls
The destination may be a phonebook name, extension, group, SIP URI or public number.
From a card, select a contact or enter the destination and press Call.
From an automation:
action: voip_stack.call
data:
device_id: 0123456789abcdef0123456789abcdef
destination: Kitchen
From an ESPHome phone, choose a contact and invoke its normal call action. The ESP calls the selected destination through the route published by Home Assistant. Direct ESP-to-ESP SIP remains possible when the phonebook contains a direct route.
The public actions are:
| Action | Purpose |
|---|---|
voip_stack.call | Start a call from the selected local phone |
voip_stack.answer | Answer a pending call |
voip_stack.decline | Reject a pending call |
voip_stack.hangup | End the selected call |
voip_stack.forward | Redirect a pending or ringing HA-owned call |
voip_stack.transfer | Transfer an established call with SIP REFER |
voip_stack.set_dnd | Change DND on an HA phone |
voip_stack.set_auto_answer | Persist Auto Answer for an HA phone |
voip_stack.set_send_video | Persist default camera transmission |
Add more Home Assistant phones
Open Settings > Devices & services > VoIP Stack > Add phone > Home Assistant browser phone.
Create one phone per independent place or browser session, for example:
Casa, extension666;Test, extension667;Reception, extension200.
Each phone receives its own Device, call state, DND, Auto Answer, extension, groups and video settings. Bind each card to the intended Device. Two cards in one browser tab do not create two physical microphones or cameras, so use a separate browser or Companion session for simultaneous independent media.
Register a SIP client or IP phone
- Enable the local registrar in VoIP Stack > Configure.
- Create an account through Add phone > SIP account, or use the
voip_stack.create_accountaction. - Configure the SIP client with the Home Assistant advertise host, SIP port, username and generated password.
- Use UDP or TCP according to the account and network configuration.
The registered client appears in the central phonebook while at least one Contact binding is active. Account passwords are returned only by the account management action and are not placed in entity state.
Configure groups
Use phone Device entities or phone settings to assign comma-separated groups.
- A ring group rings all eligible members. The first answer wins.
- A conference group joins participants to one HA audio mixer.
- Ring for conference calls controls whether a member rings when another participant starts that conference.
Call the group name exactly as shown in the phonebook.
Make Assist callable
Open VoIP Stack > Reconfigure, enable Include voice assistant, choose a pipeline and assign an extension. Assist then becomes a normal phonebook destination.
Hanging up terminates the Assist media leg and its active pipeline work. It does not leave a listening call session behind.
Configure a trunk
Leave the trunk disabled for a local-only installation. To connect FRITZ!Box, Wildix, another PBX or a provider:
- Open VoIP Stack > Reconfigure > Trunk.
- Enter server, port, transport, domain and credentials.
- Set an outbound proxy only when the PBX requires one.
- Select the default inbound destination.
- Enable digit collection only when callers must choose an internal extension.
Public numbers and service strings such as * or **621 are sent to the trunk
without destructive normalization. Names and internal extensions are resolved
through the phonebook first.
Home Assistant trunk and standard SIP legs support UDP, TCP and verified TLS. ESP endpoints intentionally remain lightweight local SIP/RTP phones and do not terminate SIP TLS or SRTP.
Forward or transfer a call
Forward a call before it is established:
action: voip_stack.forward
data:
device_id: 0123456789abcdef0123456789abcdef
call_id: current-call-id
destination: Reception
Transfer an established call:
action: voip_stack.transfer
data:
device_id: 0123456789abcdef0123456789abcdef
call_id: current-call-id
destination: sip:desk@pbx.example
Secure SIP identities such as sips:desk@pbx.example:5061;transport=tls are
preserved across direct routing, outbound proxies and REFER targets.
Audio and video calls
Browser phones negotiate supported audio codecs and can send or receive video. ESP32-P4 profiles provide either JPEG or H.264 SIP video according to the YAML selected at compile time.
An audio call can add compatible video through re-INVITE. The previous media contract remains active until both dialogs accept the change. Closing the camera or browser track does not independently rewrite the PBX call state.
For browser calls:
- microphone permission is required before Answer or Call can start media;
- camera permission is required only to transmit video;
- receiving a remote camera does not require local camera permission;
- reset the Companion frontend cache after upgrading the bundled card.
Health, repairs and diagnostics
Use Settings > System > Repairs for actionable problems such as missing ESPHome call-control actions, incompatible firmware contracts or enabled media capture.
Use Settings > System > Repairs > System information or the integration's System Health entry to inspect aggregate listener, trunk, endpoint, active-call and RTP-resource status. Diagnostics redact credentials, private identities and complete Call-IDs.
If a call fails:
- check that both phone Devices are available;
- check DND and Auto Answer;
- verify the destination in the phonebook;
- verify SIP and RTP firewall rules;
- read
troubleshooting.md; - collect diagnostics only after reproducing the failure.
Upgrade safely
- Read
BREAKING_CHANGES.md. - Download the update through HACS and restart Home Assistant.
- Open Reconfigure once and review the integration options.
- Open every card and confirm its selected phone Device.
- Reset the browser or Companion frontend cache.
- Rebuild ESPHome firmware when phone packages or component contracts changed.
Do not restore endpoint_id in card YAML and do not copy old action fields such
as target, source or entity_id into the current phone actions.