Skip to main content
Selora Homes Selora Homes
Recipe · v1.0.0 · Released Aug 28, 2026

Leak Auto Shutoff

Installs a Home Assistant automation that closes your water shutoff valve the instant a leak sensor goes wet, so a burst hose or a failed valve stops flooding within seconds. Add a smart plug to cut the appliance, a siren, and a speaker.

A notification about a leak is only as fast as the person reading it. A washing machine hose that lets go while the house is empty can put through gallons a minute for hours. This recipe does not wait for you. The moment any leak sensor goes wet it drives your shutoff valve closed, so the water stops within seconds of the first drop reaching a sensor.

This is the companion to the notify-only Water Leak Alert recipe. That one tells you; this one acts, and tells you as well. It needs a motorised shutoff valve on a relay, so it is a step up in hardware, and worth it on the mains feed or the line to a washing machine or water heater.

Closing the valve the right way

Motorised valves are not consistent about which way a relay closes them. Some close when the relay switches off, others when it switches on. The recipe asks you which, defaulting to closing on off, so it matches your valve rather than assuming. Once it closes the valve it leaves it closed: reopening is deliberate, on you, after you have found and dealt with the leak, so a sensor drying out does not quietly turn the water back on.

Cutting the appliance and raising the alarm

Add the appliance’s smart plug as the optional role and the recipe switches it off with the valve, so a washing machine pump is not left running against a closed valve. Add a siren and it sounds, so a leak in a room no one is in is not silent while the water is cut. Add speakers and the shutoff is announced out loud, over a speech engine your Home Assistant already has and with no paid subscription needed.

Files Selora AI's pipeline reads to install this recipe. The manifest declares roles, inputs, and integrations; the package files are Jinja-templated YAML applied to Home Assistant.

# Leak Auto Shutoff — close a motorised shutoff valve the moment any leak
# sensor goes wet, and optionally cut the appliance, sound a siren, and
# announce it.
#
# Distinct from the notify-only water-leak-alert recipe: this one acts. The
# relay role drives the valve; a select input covers valves that close on off
# and valves that close on on, rendered straight into the service name
# (switch.turn_off or switch.turn_on). The valve is left closed after a leak,
# never reopened automatically, so drying out does not restore the water.
slug: leak-auto-shutoff
version: 1.0.0
title: Leak Auto Shutoff
tagline: A wet leak sensor closes the shutoff valve within seconds, and can cut the appliance and raise the alarm.
description: >-
  Closes a motorised shutoff valve the instant any leak sensor goes wet, so a
  burst hose stops flooding within seconds rather than waiting on a
  notification. Optionally switches off the appliance's smart plug, sounds a
  siren, and announces over TTS. Leaves the valve closed until you reopen it.
author: Selora AI
released: "2026-08-28"
min_integration_version: "0.12.0"
tags: [safety, water-leak, shutoff]
roles:
  # Leak sensors — multi-select. binary_sensor + device_class moisture.
  # min_count 1.
  - id: leak_sensors
    title: Water leak sensors
    kind: binary_sensor
    device_class: moisture
    min_count: 1
    selection: required
    description: >-
      The leak sensors that trigger the shutoff. Any one going wet closes the
      valve.
  # Shutoff valve relay — the valve control. kind switch. Single: one main
  # valve. min_count 1.
  - id: shutoff_relay
    title: Shutoff valve relay
    kind: switch
    min_count: 1
    max_count: 1
    selection: required
    description: >-
      The relay wired to your motorised shutoff valve. The recipe drives it to
      close the valve.
  # Optional appliance smart plugs cut with the valve. kind switch. min_count 0.
  - id: shed_plugs
    title: Appliance smart plugs
    kind: switch
    min_count: 0
    selection: required
    description: >-
      Optional. Smart plugs carrying the appliance behind the leak, switched off
      with the valve. Leave empty to close the valve only.
  # Optional sirens. kind siren. min_count 0.
  - id: sirens
    title: Sirens
    kind: siren
    min_count: 0
    selection: required
    description: >-
      Optional. Sirens that sound when the valve closes. Leave empty for a
      silent shutoff.
  # Optional announcement speakers over TTS. media_player. min_count 0.
  - id: announce_speakers
    title: Announcement speakers
    kind: media_player
    min_count: 0
    selection: required
    description: >-
      Optional. Speakers that announce the shutoff over TTS in addition to the
      push notification. Leave empty to be notified on your phone only.
inputs:
  # How the relay closes the valve. Some valves close when the relay switches
  # off, others when it switches on. Rendered into the service name.
  # How long the optional siren sounds. A leak siren with no stop path runs
  # until somebody comes home to it, so the recipe owns switching it off the
  # same way the away-motion recipe does. Longer than that recipe's 30 seconds
  # because a leak should keep sounding long enough to be noticed from another
  # floor, but still bounded.
  - id: siren_seconds
    type: number
    label: Siren duration (seconds)
    description: >-
      How long the optional siren sounds after the valve closes.
    default: 120
    min: 1
    max: 600
  - id: shutoff_command
    type: select
    label: Closing the valve
    description: >-
      Whether the relay closes the valve by switching off or switching on. Match
      it to your valve.
    default: "turn_off"
    choices: ["turn_off", "turn_on"]
  # TTS engine entity for the optional spoken announcement. Auto-resolved by the
  # tts_engine resolver from the home's configured tts.* entities, so the wizard
  # never shows this field. tts.speak works without a paid Home Assistant Cloud
  # subscription. The resolver returns an empty string when the home has no TTS
  # engine, and the template then skips the announcement. The default below is
  # only used by the offline recipe-validation gate.
  - id: tts_engine
    type: string
    label: Text-to-Speech engine
    description: >-
      Speech engine entity used for the spoken announcement. Resolved
      automatically from the Text-to-Speech engines configured in Home
      Assistant.
    resolver: tts_engine
    default: "tts.piper"
package_files:
  - package/automations/shutoff.yaml.j2
{# Leak Auto Shutoff: on any leak sensor going wet, close the valve, optionally
   cut the appliance plugs, notify, and optionally sound the siren and announce
   over TTS.

   The valve is closed by rendering the chosen command straight into the
   service name (switch.turn_off or switch.turn_on), so the recipe matches how
   the installer's valve is wired. The recipe then waits, briefly, for the
   relay to report shut, and the notification and announcement say plainly
   whether it did, because a leak alert that claims a closed valve it never
   closed is worse than no alert at all. What a relay reports is the relay, not
   the water, so the wording asks the reader to confirm the flow has stopped
   rather than telling them it has. It never reopens the valve: after a leak the
   valve stays closed until a person reopens it, so a sensor drying out does not
   silently restore the water.

   mode: single, and the leak sensors are watched for the wet state only, so a
   second sensor tripping while the first is still handled does not re-run.

   A second automation switches the siren off once it has sounded for its set
   time. siren.turn_on does not oblige an entity to stop by itself, so without
   it a leak at the start of a week away would leave the alarm sounding until
   somebody came home to it. #}
{# The relay state that means "shut", derived from the wiring choice at install
   time so the alert can check whether the valve actually got there. #}
{% set shut_state = 'off' if inputs.shutoff_command == 'turn_off' else 'on' %}
automation:
  - id: selora_recipe_{{ slug | replace('-', '_') }}_shutoff
    alias: "Leak Auto Shutoff: close valve on leak"
    description: >-
      When any leak sensor goes wet, close the shutoff valve and raise the
      alarm.
    mode: single
    trigger:
{% for sensor in roles.leak_sensors %}
      - platform: state
        entity_id: {{ sensor }}
        to: "on"
{% endfor %}
    action:
      - variables:
          source: "{% raw %}{{ trigger.to_state.name | default(trigger.entity_id, true) }}{% endraw %}"
      # continue_on_error on both device calls. An unavailable relay or plug
      # raises, and without this the sequence aborts before the notification,
      # the siren and the announcement: the one moment the household most needs
      # telling is the moment it would hear nothing at all. Failing to close the
      # valve must make the alert louder, not silence it.
      - service: switch.{{ inputs.shutoff_command }}
        continue_on_error: true
        target:
          entity_id: {{ roles.shutoff_relay[0] }}
{% if roles.shed_plugs %}
      - service: switch.turn_off
        continue_on_error: true
        target:
          entity_id:
{% for plug in roles.shed_plugs %}
            - {{ plug }}
{% endfor %}
{% endif %}
      # Wait for the relay to report shut rather than reading it the instant
      # after the command. Many relays report optimistically or a second or two
      # late, and an immediate read turns that lag into a false alarm. Ten
      # seconds is long enough for a relay to answer and short enough that a
      # genuine failure still raises the alarm promptly. continue_on_timeout so
      # a silent relay falls through to the warning instead of ending the run.
      - wait_template: "{% raw %}{{ is_state('{% endraw %}{{ roles.shutoff_relay[0] }}{% raw %}', '{% endraw %}{{ shut_state }}{% raw %}') }}{% endraw %}"
        timeout: "00:00:10"
        continue_on_timeout: true
      - variables:
          valve_shut: "{% raw %}{{ wait.completed }}{% endraw %}"
      - service: notify.notify
        data:
          title: "{% raw %}{{ 'Water leak: valve reports closed' if valve_shut else 'WATER LEAK: valve has NOT reported closed' }}{% endraw %}"
          message: "{% raw %}{{ 'Water was detected by ' ~ source ~ '. The shutoff valve was told to close and reports closed. Check the water has actually stopped, then find and clear the leak before reopening it.' if valve_shut else 'Water was detected by ' ~ source ~ ' and the shutoff valve has not reported closed. Go and shut the water off by hand now.' }}{% endraw %}"
{% if roles.sirens %}
      - service: siren.turn_on
        target:
          entity_id:
{% for siren in roles.sirens %}
            - {{ siren }}
{% endfor %}
{% endif %}
{% if roles.announce_speakers and inputs.tts_engine %}
      - service: tts.speak
        continue_on_error: true
        target:
          entity_id: {{ inputs.tts_engine }}
        data:
          media_player_entity_id:
{% for player in roles.announce_speakers %}
            - {{ player }}
{% endfor %}
          message: "{% raw %}{{ 'A water leak was detected. The shutoff valve reports closed. Check the water has stopped.' if valve_shut else 'A water leak was detected and the shutoff valve has not reported closed. Shut the water off by hand now.' }}{% endraw %}"
{% endif %}
{% if roles.sirens %}
  - id: selora_recipe_{{ slug | replace('-', '_') }}_siren_stop
    alias: "Leak Auto Shutoff: stop the siren"
    description: >-
      Switch the siren off once it has been sounding for its set time.
    mode: single
    trigger:
{% for siren in roles.sirens %}
      - platform: state
        entity_id: {{ siren }}
        to: "on"
        for:
          seconds: {{ inputs.siren_seconds }}
{% endfor %}
      # A state trigger's for: timer is discarded on restart, and a siren that
      # is already on never transitions to on again, so the timed stop would
      # never arrive and the alarm would sound until someone silenced it by
      # hand. At startup any siren still sounding has already been going since
      # before the restart, so the right move is to stop it.
      - platform: homeassistant
        event: start
    condition:
      - condition: or
        conditions:
{% for siren in roles.sirens %}
          - condition: state
            entity_id: {{ siren }}
            state: "on"
{% endfor %}
    action:
      - service: siren.turn_off
        target:
          entity_id:
{% for siren in roles.sirens %}
            - {{ siren }}
{% endfor %}
{% endif %}

Type to search across

↑↓ navigate ↵ open
⌘K search