diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py
index 2eac4b7d7b0..f17654f184a 100644
--- a/homeassistant/components/zwave_js/device_condition.py
+++ b/homeassistant/components/zwave_js/device_condition.py
@@ -4,9 +4,12 @@ from __future__ import annotations
 from typing import cast
 
 import voluptuous as vol
-from zwave_js_server.const import CommandClass, ConfigurationValueType
+from zwave_js_server.const import CommandClass
 from zwave_js_server.model.value import ConfigurationValue
 
+from homeassistant.components.device_automation.exceptions import (
+    InvalidDeviceAutomationConfig,
+)
 from homeassistant.const import CONF_CONDITION, CONF_DEVICE_ID, CONF_DOMAIN, CONF_TYPE
 from homeassistant.core import HomeAssistant, callback
 from homeassistant.exceptions import HomeAssistantError
@@ -22,7 +25,14 @@ from .const import (
     ATTR_PROPERTY_KEY,
     ATTR_VALUE,
 )
-from .helpers import async_get_node_from_device_id, get_zwave_value_from_config
+from .helpers import (
+    async_get_node_from_device_id,
+    async_is_device_config_entry_not_loaded,
+    check_type_schema_map,
+    get_value_state_schema,
+    get_zwave_value_from_config,
+    remove_keys_with_empty_values,
+)
 
 CONF_SUBTYPE = "subtype"
 CONF_VALUE_ID = "value_id"
@@ -67,10 +77,21 @@ VALUE_CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend(
     }
 )
 
-CONDITION_SCHEMA = vol.Any(
-    NODE_STATUS_CONDITION_SCHEMA,
-    CONFIG_PARAMETER_CONDITION_SCHEMA,
-    VALUE_CONDITION_SCHEMA,
+TYPE_SCHEMA_MAP = {
+    NODE_STATUS_TYPE: NODE_STATUS_CONDITION_SCHEMA,
+    CONFIG_PARAMETER_TYPE: CONFIG_PARAMETER_CONDITION_SCHEMA,
+    VALUE_TYPE: VALUE_CONDITION_SCHEMA,
+}
+
+
+CONDITION_TYPE_SCHEMA = vol.Schema(
+    {vol.Required(CONF_TYPE): vol.In(TYPE_SCHEMA_MAP)}, extra=vol.ALLOW_EXTRA
+)
+
+CONDITION_SCHEMA = vol.All(
+    remove_keys_with_empty_values,
+    CONDITION_TYPE_SCHEMA,
+    check_type_schema_map(TYPE_SCHEMA_MAP),
 )
 
 
@@ -79,9 +100,18 @@ async def async_validate_condition_config(
 ) -> ConfigType:
     """Validate config."""
     config = CONDITION_SCHEMA(config)
+
+    # We return early if the config entry for this device is not ready because we can't
+    # validate the value without knowing the state of the device
+    if async_is_device_config_entry_not_loaded(hass, config[CONF_DEVICE_ID]):
+        return config
+
     if config[CONF_TYPE] == VALUE_TYPE:
-        node = async_get_node_from_device_id(hass, config[CONF_DEVICE_ID])
-        get_zwave_value_from_config(node, config)
+        try:
+            node = async_get_node_from_device_id(hass, config[CONF_DEVICE_ID])
+            get_zwave_value_from_config(node, config)
+        except vol.Invalid as err:
+            raise InvalidDeviceAutomationConfig(err.msg) from err
 
     return config
 
@@ -174,20 +204,8 @@ async def async_get_condition_capabilities(
     # Add additional fields to the automation trigger UI
     if config[CONF_TYPE] == CONFIG_PARAMETER_TYPE:
         value_id = config[CONF_VALUE_ID]
-        config_value = cast(ConfigurationValue, node.values[value_id])
-        min_ = config_value.metadata.min
-        max_ = config_value.metadata.max
-
-        if config_value.configuration_value_type in (
-            ConfigurationValueType.RANGE,
-            ConfigurationValueType.MANUAL_ENTRY,
-        ):
-            value_schema = vol.Range(min=min_, max=max_)
-        elif config_value.configuration_value_type == ConfigurationValueType.ENUMERATED:
-            value_schema = vol.In(
-                {int(k): v for k, v in config_value.metadata.states.items()}
-            )
-        else:
+        value_schema = get_value_state_schema(node.values[value_id])
+        if not value_schema:
             return {}
 
         return {"extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema})}
diff --git a/homeassistant/components/zwave_js/device_trigger.py b/homeassistant/components/zwave_js/device_trigger.py
index 6fab91c867d..7ed13ce2b98 100644
--- a/homeassistant/components/zwave_js/device_trigger.py
+++ b/homeassistant/components/zwave_js/device_trigger.py
@@ -4,10 +4,13 @@ from __future__ import annotations
 from typing import Any
 
 import voluptuous as vol
-from zwave_js_server.const import CommandClass, ConfigurationValueType
+from zwave_js_server.const import CommandClass
 
 from homeassistant.components.automation import AutomationActionType
 from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
+from homeassistant.components.device_automation.exceptions import (
+    InvalidDeviceAutomationConfig,
+)
 from homeassistant.components.homeassistant.triggers import event, state
 from homeassistant.const import (
     CONF_DEVICE_ID,
@@ -46,12 +49,20 @@ from .const import (
 from .helpers import (
     async_get_node_from_device_id,
     async_get_node_status_sensor_entity_id,
+    async_is_device_config_entry_not_loaded,
+    check_type_schema_map,
+    copy_available_params,
+    get_value_state_schema,
     get_zwave_value_from_config,
+    remove_keys_with_empty_values,
+)
+from .triggers.value_updated import (
+    ATTR_FROM,
+    ATTR_TO,
+    PLATFORM_TYPE as VALUE_UPDATED_PLATFORM_TYPE,
 )
-from .triggers.value_updated import ATTR_FROM, ATTR_TO
 
 CONF_SUBTYPE = "subtype"
-CONF_VALUE_ID = "value_id"
 
 # Trigger types
 ENTRY_CONTROL_NOTIFICATION = "event.notification.entry_control"
@@ -59,8 +70,8 @@ NOTIFICATION_NOTIFICATION = "event.notification.notification"
 BASIC_VALUE_NOTIFICATION = "event.value_notification.basic"
 CENTRAL_SCENE_VALUE_NOTIFICATION = "event.value_notification.central_scene"
 SCENE_ACTIVATION_VALUE_NOTIFICATION = "event.value_notification.scene_activation"
-CONFIG_PARAMETER_VALUE_UPDATED = f"{DOMAIN}.value_updated.config_parameter"
-VALUE_VALUE_UPDATED = f"{DOMAIN}.value_updated.value"
+CONFIG_PARAMETER_VALUE_UPDATED = f"{VALUE_UPDATED_PLATFORM_TYPE}.config_parameter"
+VALUE_VALUE_UPDATED = f"{VALUE_UPDATED_PLATFORM_TYPE}.value"
 NODE_STATUS = "state.node_status"
 
 VALUE_SCHEMA = vol.Any(
@@ -71,6 +82,7 @@ VALUE_SCHEMA = vol.Any(
     cv.string,
 )
 
+
 NOTIFICATION_EVENT_CC_MAPPINGS = (
     (ENTRY_CONTROL_NOTIFICATION, CommandClass.ENTRY_CONTROL),
     (NOTIFICATION_NOTIFICATION, CommandClass.NOTIFICATION),
@@ -104,7 +116,7 @@ ENTRY_CONTROL_NOTIFICATION_SCHEMA = BASE_EVENT_SCHEMA.extend(
 BASE_VALUE_NOTIFICATION_EVENT_SCHEMA = BASE_EVENT_SCHEMA.extend(
     {
         vol.Required(ATTR_PROPERTY): vol.Any(int, str),
-        vol.Required(ATTR_PROPERTY_KEY): vol.Any(None, int, str),
+        vol.Optional(ATTR_PROPERTY_KEY): vol.Any(int, str),
         vol.Required(ATTR_ENDPOINT): vol.Coerce(int),
         vol.Optional(ATTR_VALUE): vol.Coerce(int),
         vol.Required(CONF_SUBTYPE): cv.string,
@@ -174,17 +186,61 @@ VALUE_VALUE_UPDATED_SCHEMA = BASE_VALUE_UPDATED_SCHEMA.extend(
     }
 )
 
-TRIGGER_SCHEMA = vol.Any(
-    ENTRY_CONTROL_NOTIFICATION_SCHEMA,
-    NOTIFICATION_NOTIFICATION_SCHEMA,
-    BASIC_VALUE_NOTIFICATION_SCHEMA,
-    CENTRAL_SCENE_VALUE_NOTIFICATION_SCHEMA,
-    SCENE_ACTIVATION_VALUE_NOTIFICATION_SCHEMA,
-    CONFIG_PARAMETER_VALUE_UPDATED_SCHEMA,
-    VALUE_VALUE_UPDATED_SCHEMA,
-    NODE_STATUS_SCHEMA,
+TYPE_SCHEMA_MAP = {
+    ENTRY_CONTROL_NOTIFICATION: ENTRY_CONTROL_NOTIFICATION_SCHEMA,
+    NOTIFICATION_NOTIFICATION: NOTIFICATION_NOTIFICATION_SCHEMA,
+    BASIC_VALUE_NOTIFICATION: BASIC_VALUE_NOTIFICATION_SCHEMA,
+    CENTRAL_SCENE_VALUE_NOTIFICATION: CENTRAL_SCENE_VALUE_NOTIFICATION_SCHEMA,
+    SCENE_ACTIVATION_VALUE_NOTIFICATION: SCENE_ACTIVATION_VALUE_NOTIFICATION_SCHEMA,
+    CONFIG_PARAMETER_VALUE_UPDATED: CONFIG_PARAMETER_VALUE_UPDATED_SCHEMA,
+    VALUE_VALUE_UPDATED: VALUE_VALUE_UPDATED_SCHEMA,
+    NODE_STATUS: NODE_STATUS_SCHEMA,
+}
+
+
+TRIGGER_TYPE_SCHEMA = vol.Schema(
+    {vol.Required(CONF_TYPE): vol.In(TYPE_SCHEMA_MAP)}, extra=vol.ALLOW_EXTRA
 )
 
+TRIGGER_SCHEMA = vol.All(
+    remove_keys_with_empty_values,
+    TRIGGER_TYPE_SCHEMA,
+    check_type_schema_map(TYPE_SCHEMA_MAP),
+)
+
+
+async def async_validate_trigger_config(
+    hass: HomeAssistant, config: ConfigType
+) -> ConfigType:
+    """Validate config."""
+    config = TRIGGER_SCHEMA(config)
+
+    # We return early if the config entry for this device is not ready because we can't
+    # validate the value without knowing the state of the device
+    if async_is_device_config_entry_not_loaded(hass, config[CONF_DEVICE_ID]):
+        return config
+
+    trigger_type = config[CONF_TYPE]
+    if get_trigger_platform_from_type(trigger_type) == VALUE_UPDATED_PLATFORM_TYPE:
+        try:
+            node = async_get_node_from_device_id(hass, config[CONF_DEVICE_ID])
+            get_zwave_value_from_config(node, config)
+        except vol.Invalid as err:
+            raise InvalidDeviceAutomationConfig(err.msg) from err
+
+    return config
+
+
+def get_trigger_platform_from_type(trigger_type: str) -> str:
+    """Get trigger platform from Z-Wave JS trigger type."""
+    trigger_split = trigger_type.split(".")
+    # Our convention for trigger types is to have the trigger type at the beginning
+    # delimited by a `.`. For zwave_js triggers, there is a `.` in the name
+    trigger_platform = trigger_split[0]
+    if trigger_platform == DOMAIN:
+        return ".".join(trigger_split[:2])
+    return trigger_platform
+
 
 async def async_get_triggers(
     hass: HomeAssistant, device_id: str
@@ -298,15 +354,6 @@ async def async_get_triggers(
     return triggers
 
 
-def copy_available_params(
-    input_dict: dict, output_dict: dict, params: list[str]
-) -> None:
-    """Copy available params from input into output."""
-    for param in params:
-        if (val := input_dict.get(param)) not in ("", None):
-            output_dict[param] = val
-
-
 async def async_attach_trigger(
     hass: HomeAssistant,
     config: ConfigType,
@@ -315,12 +362,7 @@ async def async_attach_trigger(
 ) -> CALLBACK_TYPE:
     """Attach a trigger."""
     trigger_type = config[CONF_TYPE]
-    trigger_split = trigger_type.split(".")
-    # Our convention for trigger types is to have the trigger type at the beginning
-    # delimited by a `.`. For zwave_js triggers, there is a `.` in the name
-    trigger_platform = trigger_split[0]
-    if trigger_platform == DOMAIN:
-        trigger_platform = ".".join(trigger_split[:2])
+    trigger_platform = get_trigger_platform_from_type(trigger_type)
 
     # Take input data from automation trigger UI and add it to the trigger we are
     # attaching to
@@ -379,14 +421,7 @@ async def async_attach_trigger(
             hass, state_config, action, automation_info, platform_type="device"
         )
 
-    if trigger_platform == f"{DOMAIN}.value_updated":
-        # Try to get the value to make sure the value ID is valid
-        try:
-            node = async_get_node_from_device_id(hass, config[CONF_DEVICE_ID])
-            get_zwave_value_from_config(node, config)
-        except (ValueError, vol.Invalid) as err:
-            raise HomeAssistantError("Invalid value specified") from err
-
+    if trigger_platform == VALUE_UPDATED_PLATFORM_TYPE:
         zwave_js_config = {
             state.CONF_PLATFORM: trigger_platform,
             CONF_DEVICE_ID: config[CONF_DEVICE_ID],
@@ -420,9 +455,7 @@ async def async_get_trigger_capabilities(
     trigger_type = config[CONF_TYPE]
 
     node = async_get_node_from_device_id(hass, config[CONF_DEVICE_ID])
-    value = (
-        get_zwave_value_from_config(node, config) if ATTR_PROPERTY in config else None
-    )
+
     # Add additional fields to the automation trigger UI
     if trigger_type == NOTIFICATION_NOTIFICATION:
         return {
@@ -462,33 +495,23 @@ async def async_get_trigger_capabilities(
         CENTRAL_SCENE_VALUE_NOTIFICATION,
         SCENE_ACTIVATION_VALUE_NOTIFICATION,
     ):
-        if value.metadata.states:
-            value_schema = vol.In({int(k): v for k, v in value.metadata.states.items()})
-        else:
-            value_schema = vol.All(
-                vol.Coerce(int),
-                vol.Range(min=value.metadata.min, max=value.metadata.max),
-            )
+        value_schema = get_value_state_schema(get_zwave_value_from_config(node, config))
+
+        # We should never get here, but just in case we should add a guard
+        if not value_schema:
+            return {}
 
         return {"extra_fields": vol.Schema({vol.Optional(ATTR_VALUE): value_schema})}
 
     if trigger_type == CONFIG_PARAMETER_VALUE_UPDATED:
-        # We can be more deliberate about the config parameter schema here because
-        # there are a limited number of types
-        if value.configuration_value_type == ConfigurationValueType.UNDEFINED:
+        value_schema = get_value_state_schema(get_zwave_value_from_config(node, config))
+        if not value_schema:
             return {}
-        if value.configuration_value_type == ConfigurationValueType.ENUMERATED:
-            value_schema = vol.In({int(k): v for k, v in value.metadata.states.items()})
-        else:
-            value_schema = vol.All(
-                vol.Coerce(int),
-                vol.Range(min=value.metadata.min, max=value.metadata.max),
-            )
         return {
             "extra_fields": vol.Schema(
                 {
-                    vol.Optional(state.CONF_FROM): value_schema,
-                    vol.Optional(state.CONF_TO): value_schema,
+                    vol.Optional(ATTR_FROM): value_schema,
+                    vol.Optional(ATTR_TO): value_schema,
                 }
             )
         }
@@ -509,8 +532,8 @@ async def async_get_trigger_capabilities(
                     vol.Required(ATTR_PROPERTY): cv.string,
                     vol.Optional(ATTR_PROPERTY_KEY): cv.string,
                     vol.Optional(ATTR_ENDPOINT): cv.string,
-                    vol.Optional(state.CONF_FROM): cv.string,
-                    vol.Optional(state.CONF_TO): cv.string,
+                    vol.Optional(ATTR_FROM): cv.string,
+                    vol.Optional(ATTR_TO): cv.string,
                 }
             )
         }
diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py
index 667d7a9de24..4744c7f9fc1 100644
--- a/homeassistant/components/zwave_js/helpers.py
+++ b/homeassistant/components/zwave_js/helpers.py
@@ -1,16 +1,21 @@
 """Helper functions for Z-Wave JS integration."""
 from __future__ import annotations
 
-from typing import Any, cast
+from typing import Any, Callable, cast
 
 import voluptuous as vol
 from zwave_js_server.client import Client as ZwaveClient
+from zwave_js_server.const import ConfigurationValueType
 from zwave_js_server.model.node import Node as ZwaveNode
-from zwave_js_server.model.value import Value as ZwaveValue, get_value_id
+from zwave_js_server.model.value import (
+    ConfigurationValue,
+    Value as ZwaveValue,
+    get_value_id,
+)
 
 from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
-from homeassistant.config_entries import ConfigEntry
-from homeassistant.const import __version__ as HA_VERSION
+from homeassistant.config_entries import ConfigEntry, ConfigEntryState
+from homeassistant.const import CONF_TYPE, __version__ as HA_VERSION
 from homeassistant.core import HomeAssistant, callback
 from homeassistant.exceptions import HomeAssistantError
 from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -242,3 +247,69 @@ def async_get_node_status_sensor_entity_id(
         )
 
     return entity_id
+
+
+def remove_keys_with_empty_values(config: ConfigType) -> ConfigType:
+    """Remove keys from config where the value is an empty string or None."""
+    return {key: value for key, value in config.items() if value not in ("", None)}
+
+
+def check_type_schema_map(schema_map: dict[str, vol.Schema]) -> Callable:
+    """Check type specific schema against config."""
+
+    def _check_type_schema(config: ConfigType) -> ConfigType:
+        """Check type specific schema against config."""
+        return cast(ConfigType, schema_map[str(config[CONF_TYPE])](config))
+
+    return _check_type_schema
+
+
+def copy_available_params(
+    input_dict: dict[str, Any], output_dict: dict[str, Any], params: list[str]
+) -> None:
+    """Copy available params from input into output."""
+    output_dict.update(
+        {param: input_dict[param] for param in params if param in input_dict}
+    )
+
+
+@callback
+def async_is_device_config_entry_not_loaded(
+    hass: HomeAssistant, device_id: str
+) -> bool:
+    """Return whether device's config entries are not loaded."""
+    dev_reg = dr.async_get(hass)
+    device = dev_reg.async_get(device_id)
+    assert device
+    return any(
+        (entry := hass.config_entries.async_get_entry(entry_id))
+        and entry.state != ConfigEntryState.LOADED
+        for entry_id in device.config_entries
+    )
+
+
+def get_value_state_schema(
+    value: ZwaveValue,
+) -> vol.Schema | None:
+    """Return device automation schema for a config entry."""
+    if isinstance(value, ConfigurationValue):
+        min_ = value.metadata.min
+        max_ = value.metadata.max
+        if value.configuration_value_type in (
+            ConfigurationValueType.RANGE,
+            ConfigurationValueType.MANUAL_ENTRY,
+        ):
+            return vol.All(vol.Coerce(int), vol.Range(min=min_, max=max_))
+
+        if value.configuration_value_type == ConfigurationValueType.ENUMERATED:
+            return vol.In({int(k): v for k, v in value.metadata.states.items()})
+
+        return None
+
+    if value.metadata.states:
+        return vol.In({int(k): v for k, v in value.metadata.states.items()})
+
+    return vol.All(
+        vol.Coerce(int),
+        vol.Range(min=value.metadata.min, max=value.metadata.max),
+    )
diff --git a/tests/components/zwave_js/test_device_condition.py b/tests/components/zwave_js/test_device_condition.py
index 73ac9957071..dfdbb16c8e8 100644
--- a/tests/components/zwave_js/test_device_condition.py
+++ b/tests/components/zwave_js/test_device_condition.py
@@ -10,6 +10,9 @@ from zwave_js_server.const import CommandClass
 from zwave_js_server.event import Event
 
 from homeassistant.components import automation
+from homeassistant.components.device_automation.exceptions import (
+    InvalidDeviceAutomationConfig,
+)
 from homeassistant.components.zwave_js import DOMAIN, device_condition
 from homeassistant.components.zwave_js.helpers import get_zwave_value_from_config
 from homeassistant.exceptions import HomeAssistantError
@@ -519,6 +522,7 @@ async def test_get_condition_capabilities_config_parameter(
         {
             "name": "value",
             "required": True,
+            "type": "integer",
             "valueMin": 0,
             "valueMax": 124,
         }
@@ -565,6 +569,30 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration):
             == {}
         )
 
+    INVALID_CONFIG = {
+        "condition": "device",
+        "domain": DOMAIN,
+        "device_id": device.id,
+        "type": "value",
+        "command_class": CommandClass.DOOR_LOCK.value,
+        "property": 9999,
+        "property_key": 9999,
+        "endpoint": 9999,
+        "value": 9999,
+    }
+
+    # Test that invalid config raises exception
+    with pytest.raises(InvalidDeviceAutomationConfig):
+        await device_condition.async_validate_condition_config(hass, INVALID_CONFIG)
+
+    # Unload entry so we can verify that validation will pass on an invalid config
+    # since we return early
+    await hass.config_entries.async_unload(integration.entry_id)
+    assert (
+        await device_condition.async_validate_condition_config(hass, INVALID_CONFIG)
+        == INVALID_CONFIG
+    )
+
 
 async def test_get_value_from_config_failure(
     hass, client, hank_binary_switch, integration
diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py
index c7cd8e23943..22496d3deed 100644
--- a/tests/components/zwave_js/test_device_trigger.py
+++ b/tests/components/zwave_js/test_device_trigger.py
@@ -8,11 +8,10 @@ from zwave_js_server.event import Event
 from zwave_js_server.model.node import Node
 
 from homeassistant.components import automation
-from homeassistant.components.zwave_js import DOMAIN, device_trigger
-from homeassistant.components.zwave_js.device_trigger import (
-    async_attach_trigger,
-    async_get_trigger_capabilities,
+from homeassistant.components.device_automation.exceptions import (
+    InvalidDeviceAutomationConfig,
 )
+from homeassistant.components.zwave_js import DOMAIN, device_trigger
 from homeassistant.components.zwave_js.helpers import (
     async_get_node_status_sensor_entity_id,
 )
@@ -1281,12 +1280,12 @@ async def test_get_trigger_capabilities_value_updated_config_parameter_enumerate
 async def test_failure_scenarios(hass, client, hank_binary_switch, integration):
     """Test failure scenarios."""
     with pytest.raises(HomeAssistantError):
-        await async_attach_trigger(
+        await device_trigger.async_attach_trigger(
             hass, {"type": "failed.test", "device_id": "invalid_device_id"}, None, {}
         )
 
     with pytest.raises(HomeAssistantError):
-        await async_attach_trigger(
+        await device_trigger.async_attach_trigger(
             hass,
             {"type": "event.failed_type", "device_id": "invalid_device_id"},
             None,
@@ -1297,12 +1296,12 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration):
     device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
 
     with pytest.raises(HomeAssistantError):
-        await async_attach_trigger(
+        await device_trigger.async_attach_trigger(
             hass, {"type": "failed.test", "device_id": device.id}, None, {}
         )
 
     with pytest.raises(HomeAssistantError):
-        await async_attach_trigger(
+        await device_trigger.async_attach_trigger(
             hass,
             {"type": "event.failed_type", "device_id": device.id},
             None,
@@ -1310,29 +1309,13 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration):
         )
 
     with pytest.raises(HomeAssistantError):
-        await async_attach_trigger(
+        await device_trigger.async_attach_trigger(
             hass,
             {"type": "state.failed_type", "device_id": device.id},
             None,
             {},
         )
 
-    with pytest.raises(HomeAssistantError):
-        await async_attach_trigger(
-            hass,
-            {
-                "device_id": device.id,
-                "type": "zwave_js.value_updated.value",
-                "command_class": CommandClass.DOOR_LOCK.value,
-                "property": -1234,
-                "property_key": None,
-                "endpoint": None,
-                "from": "open",
-            },
-            None,
-            {},
-        )
-
     with patch(
         "homeassistant.components.zwave_js.device_trigger.async_get_node_from_device_id",
         return_value=None,
@@ -1341,7 +1324,7 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration):
         return_value=None,
     ):
         assert (
-            await async_get_trigger_capabilities(
+            await device_trigger.async_get_trigger_capabilities(
                 hass, {"type": "failed.test", "device_id": "invalid_device_id"}
             )
             == {}
@@ -1349,3 +1332,26 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration):
 
     with pytest.raises(HomeAssistantError):
         async_get_node_status_sensor_entity_id(hass, "invalid_device_id")
+
+    INVALID_CONFIG = {
+        "platform": "device",
+        "domain": DOMAIN,
+        "device_id": device.id,
+        "type": "zwave_js.value_updated.value",
+        "command_class": CommandClass.DOOR_LOCK.value,
+        "property": 9999,
+        "property_key": 9999,
+        "endpoint": 9999,
+    }
+
+    # Test that invalid config raises exception
+    with pytest.raises(InvalidDeviceAutomationConfig):
+        await device_trigger.async_validate_trigger_config(hass, INVALID_CONFIG)
+
+    # Unload entry so we can verify that validation will pass on an invalid config
+    # since we return early
+    await hass.config_entries.async_unload(integration.entry_id)
+    assert (
+        await device_trigger.async_validate_trigger_config(hass, INVALID_CONFIG)
+        == INVALID_CONFIG
+    )