Ha-sip: Inbound calls, TTS, and menus not playing well together

For background: Home Assistant OS, Core 2025.9.2, Supervisor 2025.09.0, Operating System 16.2, Frontend 20250903.5. ha-sip is currently at version 5.1; Piper TTS is at version 1.6.4 and the Wyoming Protocol is installed, current, and working. This isn’t a new problem, but I’ve finally reached the point with it where help is needed.

Abstract: inbound calls are answered by an automation which should play a single message generated by TTS, then hang up. What actually happens depends on which command: options are specified in the action. If command: play_message is used, the call is answered and the message plays on infinite repeat until the calling extension is manually hung up. With command: answer, the call is answered, but no message plays and the call remains active until the calling extension hangs up.

For comparison’s sake, there is also an automation for outbound calls using both command: dial and menu:, and it works as intended - call an extension, play the message once, hang up. This is expected behaviour, but for some reason inbound calls don’t seem to be able to be made to behave in the same way.

Here’s the incoming call action in question that plays the message in an infinite loop; note that post_action: hangup is present but does not appear to be respected.

alias: Inbound Call (Test)
description: ""
triggers:
  - trigger: webhook
    allowed_methods:
      - POST
      - PUT
    local_only: true
    webhook_id: redacted
conditions: []
actions:
  - action: hassio.addon_stdin
    metadata: {}
    data:
      addon: c7744bff_ha-sip
      input:
        command: play_message
        number: "{{ trigger.json.parsed_caller }}"
        message: >-
          This is a test of the inbound call answering system.  If this had been
          an actual inbound call, chances are good that this message wouldn't
          repeat ad infinitum.  This is only a test.  Goodbye.
        post_action: hangup
        wait_for_audio_to_finish: true
        cache_audio: false
mode: single

And here’s the command: answer action with menu: present that never plays the message and just gives dead air:

alias: Inbound Call (Test)
description: ""
triggers:
  - trigger: webhook
    allowed_methods:
      - POST
      - PUT
    local_only: true
    webhook_id: redacted
conditions: []
action: hassio.addon_stdin
metadata: {}
data:
  addon: c7744bff_ha-sip
  input:
    command: answer
    number: "{{ trigger.json.parsed_caller }}"
    menu:
      message: >-
        This is a test of the inbound call answering system.  If this had been
        an actual inbound call, chances are good that this message wouldn't
        repeat ad infinitum.  This is only a test.  Goodbye.
      post_action: hangup
      wait_for_audio_to_finish: true
      cache_audio: false
mode: single

BTW: wait_for_audio_to_finish: true and cache_audio: false are part of the requirements for the action once it’s sorted out, so need to remain in place during troubleshooting. They have been removed experimentally in both cases, but with no effect.

Does anyone have any ideas as to what may be going on? It seems to my mind that either action should work, but clearly they aren’t so something isn’t right.

Maybe this is a question to ask of the author of the add-on - it looks well maintained so you should get a response if you log an issue on the GitHub repository…

It doesn’t look like a HA automation issue.

Thanks. I’m aware of the github repo, but before opening an issue there wanted to see if this isn’t something that others have run into.

True, but at this point I also have no solid reason to believe that it’s a problem with ha-sip itself. Sanity-checking my configuration would be a good first step before opening issues, etc.

Hi Cameron,

author of ha-sip here :slight_smile:,

I guess the problem with your answer automation is, that the web-hook is called for multiple events (e.g. “audio has finished playing”), and so it will re-trigger the message playing again and again.

You need to check for the event type and only answer for incoming calls:

actions:
  - condition: template
    value_template: >-
      {{trigger.json.event == "incoming_call"}}
  - action: hassio.addon_stdin
    metadata: {}
    data:
      addon: c7744bff_ha-sip
      input:
        command: answer
        number: "{{ trigger.json.parsed_caller }}"
        menu:
          message: >-
            This is a test of the inbound call answering system.  If this had been
            an actual inbound call, chances are good that this message wouldn't
            repeat ad infinitum.  This is only a test.  Goodbye.
          post_action: hangup
          wait_for_audio_to_finish: true
          cache_audio: false

From the top of my head without testing, but you should get the point.

Hope that solves the problem. If there is anything else, it’s also helpful to take a look at the logs.

Take care,
Arne

Hi Arne,

Thank you for taking a look at this; it’s very much appreciated.

I’ve spent some time playing with this per your suggestions, and unfortunately haven’t got any further with it. Traces show that the conditions are being executed correctly, but for some reason the audio never plays if menu: is in use.

Triggering the automation based on {{trigger.json.event == "incoming_call"}} rather than the webhook does work, but then it’s back to no audio playing.

One thing that I did notice during the tests with no audio: the menu is responding to DTMF (which results in a spoken, “Could not convert text to speech,” error), leading me to believe that wait_for_audio_to_finish: isn’t being processed. Neither is post_action:, since the call never ends until the timeout is reached.

I’ve been going through the logs, and haven’t come across anything that’s really catching my eye. I’ll go through things a bit more today and see if there’s anything I’ve missed; logs and configs will follow.

Made progress and it’s working now, but not quite as intended :wink:

Basically, I ended up abandoning the idea of having everything happen through an automation and moved it all into /config/sip-1-incoming.yaml. This is OK for now, but isn’t 100% ideal for some future use cases I have in mind.

In any event, an issue (feature request, really) was opened on Github for an incoming_call webhook to be able to be defined under webhook_to_call: in /config/sip-1-incoming.yaml.

@aronym, thanks again - the help was definitely both useful and appreciated!

Cool you figured something out! I would still be interested on why the version I posted did not work for you. It worked on my machine ™. Did you see any errors in the logs?

Apologies for the delay in replying; there’s been a lot going on on my end. Anyway:

The logs never showed errors pointing to anything specific, as I recall.

What I ended up doing was leaving out sip-1-incoming.yaml entirely, setting answer_mode to listen in the add-on config, and setting value_template: to "{{ trigger.json.event == 'incoming_call' }}" in the automation.

Putting it all together, here’s the YAML for the ha-sip addon’s configuration:

sip_global:
  port: "5060"
  log_level: "5"
  name_server: 192.168.100.1
  cache_dir: /config/audio_cache
  global_options: ""
sip:
  enabled: true
  registrar_uri: sip:192.168.100.100
  id_uri: sip:[email protected]
  realm: "*"
  user_name: "9999"
  password: redacted
  answer_mode: listen
  settle_time: 1
  incoming_call_file: ""
  options: ""
sip_2:
  enabled: false
  registrar_uri: sip:192.168.100.100
  id_uri: sip:[email protected]
  realm: "*"
  user_name: "9998"
  password: redacted
  answer_mode: listen
  settle_time: 0
  incoming_call_file: ""
  options: ""
sip_3:
  enabled: false
  registrar_uri: sip:192.168.178.10
  id_uri: ""
  realm: "*"
  user_name: ""
  password: redacted
  answer_mode: listen
  settle_time: 0
  incoming_call_file: ""
  options: ""
tts:
  engine_id: tts.piper
  platform: ""
  language: en_US
  voice: en_US-hfc_female-medium
  debug_print: false
webhook:
  id: ha-sip-global

And here it is for the automation:

alias: Incoming Call (Test; Webhook)
description: ""
triggers:
  - trigger: webhook
    allowed_methods:
      - POST
      - PUT
    local_only: false
    webhook_id: redacted
conditions:
  - condition: template
    value_template: "{{ trigger.json.event == 'incoming_call' }}"
actions:
  - action: hassio.addon_stdin
    metadata: {}
    data:
      addon: c7744bff_ha-sip
      input:
        command: answer
        number: "{{ trigger.json.parsed_caller }}"
        menu:
          message: >-
            The temperature is {{ states('sensor.temperature') | round(0)
            }} degrees Celsius.  Wind speed is {{ states
            ('sensor.wind_speed') | round (0) }} miles per hour, bearing {{
            states('sensor.wind_direction') | round (0) }} degrees. 
            Pressure is {{ states('sensor.sea_level_pressure') | round (0)
            }} millibars. 
          post_action: hangup
          wait_for_audio_to_finish: true
          cache_audio: false
mode: single

This has been working reliably for nearly a month at this point. The only issue is that Piper sometimes takes up to 25 seconds to start talking after the call is answered, but that’s not an ha-sip problem :wink: