diff --git a/homeassistant/components/tedee/__init__.py b/homeassistant/components/tedee/__init__.py
index eeb0f8e0d5a005cca139c66f2691abe3fd66b4e0..cbc608d03a67f3f35b046a3d6880dd988f7bc0e2 100644
--- a/homeassistant/components/tedee/__init__.py
+++ b/homeassistant/components/tedee/__init__.py
@@ -1,12 +1,25 @@
 """Init the tedee component."""
+from collections.abc import Awaitable, Callable
+from http import HTTPStatus
 import logging
-
+from typing import Any
+
+from aiohttp.hdrs import METH_POST
+from aiohttp.web import Request, Response
+from pytedee_async.exception import TedeeWebhookException
+
+from homeassistant.components.http import HomeAssistantView
+from homeassistant.components.webhook import (
+    async_generate_url as webhook_generate_url,
+    async_register as webhook_register,
+    async_unregister as webhook_unregister,
+)
 from homeassistant.config_entries import ConfigEntry
-from homeassistant.const import Platform
+from homeassistant.const import CONF_WEBHOOK_ID, EVENT_HOMEASSISTANT_STOP, Platform
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers import device_registry as dr
 
-from .const import DOMAIN
+from .const import DOMAIN, NAME
 from .coordinator import TedeeApiCoordinator
 
 PLATFORMS = [
@@ -37,6 +50,38 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
 
     hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
 
+    async def unregister_webhook(_: Any) -> None:
+        await coordinator.async_unregister_webhook()
+        webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
+
+    async def register_webhook() -> None:
+        webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID])
+        webhook_name = "Tedee"
+        if entry.title != NAME:
+            webhook_name = f"{NAME} {entry.title}"
+
+        webhook_register(
+            hass,
+            DOMAIN,
+            webhook_name,
+            entry.data[CONF_WEBHOOK_ID],
+            get_webhook_handler(coordinator),
+            allowed_methods=[METH_POST],
+        )
+        _LOGGER.debug("Registered Tedee webhook at hass: %s", webhook_url)
+
+        try:
+            await coordinator.async_register_webhook(webhook_url)
+        except TedeeWebhookException as ex:
+            _LOGGER.warning("Failed to register Tedee webhook from bridge: %s", ex)
+        else:
+            entry.async_on_unload(
+                hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
+            )
+
+    entry.async_create_background_task(
+        hass, register_webhook(), "tedee_register_webhook"
+    )
     await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
 
     return True
@@ -45,9 +90,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Unload a config entry."""
 
-    unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
-
-    if unload_ok:
+    if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
         hass.data[DOMAIN].pop(entry.entry_id)
 
     return unload_ok
+
+
+def get_webhook_handler(
+    coordinator: TedeeApiCoordinator,
+) -> Callable[[HomeAssistant, str, Request], Awaitable[Response | None]]:
+    """Return webhook handler."""
+
+    async def async_webhook_handler(
+        hass: HomeAssistant, webhook_id: str, request: Request
+    ) -> Response | None:
+        # Handle http post calls to the path.
+        if not request.body_exists:
+            return HomeAssistantView.json(
+                result="No Body", status_code=HTTPStatus.BAD_REQUEST
+            )
+
+        body = await request.json()
+        try:
+            coordinator.webhook_received(body)
+        except TedeeWebhookException as ex:
+            return HomeAssistantView.json(
+                result=str(ex), status_code=HTTPStatus.BAD_REQUEST
+            )
+
+        return HomeAssistantView.json(result="OK", status_code=HTTPStatus.OK)
+
+    return async_webhook_handler
diff --git a/homeassistant/components/tedee/config_flow.py b/homeassistant/components/tedee/config_flow.py
index 075a4c998eaef1f263cb93c67d790434edb76c21..8bd9efd2b17fecc23139c412e4b04ca1940ebb64 100644
--- a/homeassistant/components/tedee/config_flow.py
+++ b/homeassistant/components/tedee/config_flow.py
@@ -11,8 +11,9 @@ from pytedee_async import (
 )
 import voluptuous as vol
 
+from homeassistant.components.webhook import async_generate_id as webhook_generate_id
 from homeassistant.config_entries import ConfigEntry, ConfigFlow
-from homeassistant.const import CONF_HOST
+from homeassistant.const import CONF_HOST, CONF_WEBHOOK_ID
 from homeassistant.data_entry_flow import FlowResult
 from homeassistant.helpers.aiohttp_client import async_get_clientsession
 
@@ -61,7 +62,10 @@ class TedeeConfigFlow(ConfigFlow, domain=DOMAIN):
                     return self.async_abort(reason="reauth_successful")
                 await self.async_set_unique_id(local_bridge.serial)
                 self._abort_if_unique_id_configured()
-                return self.async_create_entry(title=NAME, data=user_input)
+                return self.async_create_entry(
+                    title=NAME,
+                    data={**user_input, CONF_WEBHOOK_ID: webhook_generate_id()},
+                )
 
         return self.async_show_form(
             step_id="user",
diff --git a/homeassistant/components/tedee/coordinator.py b/homeassistant/components/tedee/coordinator.py
index c846f2a8d9a61e633dba22fe37d2f014a6b2960c..cdd907b2e5852ed5aa1a211d317c3177c3b7dba5 100644
--- a/homeassistant/components/tedee/coordinator.py
+++ b/homeassistant/components/tedee/coordinator.py
@@ -3,6 +3,7 @@ from collections.abc import Awaitable, Callable
 from datetime import timedelta
 import logging
 import time
+from typing import Any
 
 from pytedee_async import (
     TedeeClient,
@@ -10,6 +11,7 @@ from pytedee_async import (
     TedeeDataUpdateException,
     TedeeLocalAuthException,
     TedeeLock,
+    TedeeWebhookException,
 )
 from pytedee_async.bridge import TedeeBridge
 
@@ -23,7 +25,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
 
 from .const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN
 
-SCAN_INTERVAL = timedelta(seconds=20)
+SCAN_INTERVAL = timedelta(seconds=30)
 GET_LOCKS_INTERVAL_SECONDS = 3600
 
 _LOGGER = logging.getLogger(__name__)
@@ -53,6 +55,7 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
         self._next_get_locks = time.time()
         self._locks_last_update: set[int] = set()
         self.new_lock_callbacks: list[Callable[[int], None]] = []
+        self.tedee_webhook_id: int | None = None
 
     @property
     def bridge(self) -> TedeeBridge:
@@ -103,6 +106,27 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
         except (TedeeClientException, TimeoutError) as ex:
             raise UpdateFailed("Querying API failed. Error: %s" % str(ex)) from ex
 
+    def webhook_received(self, message: dict[str, Any]) -> None:
+        """Handle webhook message."""
+        self.tedee_client.parse_webhook_message(message)
+        self.async_set_updated_data(self.tedee_client.locks_dict)
+
+    async def async_register_webhook(self, webhook_url: str) -> None:
+        """Register the webhook at the Tedee bridge."""
+        self.tedee_webhook_id = await self.tedee_client.register_webhook(webhook_url)
+
+    async def async_unregister_webhook(self) -> None:
+        """Unregister the webhook at the Tedee bridge."""
+        if self.tedee_webhook_id is not None:
+            try:
+                await self.tedee_client.delete_webhook(self.tedee_webhook_id)
+            except TedeeWebhookException as ex:
+                _LOGGER.warning(
+                    "Failed to unregister Tedee webhook from bridge: %s", ex
+                )
+            else:
+                _LOGGER.debug("Unregistered Tedee webhook")
+
     def _async_add_remove_locks(self) -> None:
         """Add new locks, remove non-existing locks."""
         if not self._locks_last_update:
diff --git a/homeassistant/components/tedee/manifest.json b/homeassistant/components/tedee/manifest.json
index 09a46441e6645379fe85740e57289db01e1eb1e9..dbed87bb8900c97d32d74b7af113396af75ef64c 100644
--- a/homeassistant/components/tedee/manifest.json
+++ b/homeassistant/components/tedee/manifest.json
@@ -3,7 +3,7 @@
   "name": "Tedee",
   "codeowners": ["@patrickhilker", "@zweckj"],
   "config_flow": true,
-  "dependencies": ["http"],
+  "dependencies": ["http", "webhook"],
   "documentation": "https://www.home-assistant.io/integrations/tedee",
   "iot_class": "local_push",
   "requirements": ["pytedee-async==0.2.12"]
diff --git a/tests/components/tedee/conftest.py b/tests/components/tedee/conftest.py
index 21fb4047ab3e9b997bd972a9b79c33ed0401f8ed..a633b1642ea1135892e35defb639d17c6dc0aa28 100644
--- a/tests/components/tedee/conftest.py
+++ b/tests/components/tedee/conftest.py
@@ -10,11 +10,13 @@ from pytedee_async.lock import TedeeLock
 import pytest
 
 from homeassistant.components.tedee.const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN
-from homeassistant.const import CONF_HOST
+from homeassistant.const import CONF_HOST, CONF_WEBHOOK_ID
 from homeassistant.core import HomeAssistant
 
 from tests.common import MockConfigEntry, load_fixture
 
+WEBHOOK_ID = "bq33efxmdi3vxy55q2wbnudbra7iv8mjrq9x0gea33g4zqtd87093pwveg8xcb33"
+
 
 @pytest.fixture
 def mock_config_entry() -> MockConfigEntry:
@@ -25,6 +27,7 @@ def mock_config_entry() -> MockConfigEntry:
         data={
             CONF_LOCAL_ACCESS_TOKEN: "api_token",
             CONF_HOST: "192.168.1.42",
+            CONF_WEBHOOK_ID: WEBHOOK_ID,
         },
         unique_id="0000-0000",
     )
@@ -59,6 +62,8 @@ def mock_tedee(request) -> Generator[MagicMock, None, None]:
         tedee.get_local_bridge.return_value = TedeeBridge(0, "0000-0000", "Bridge-AB1C")
 
         tedee.parse_webhook_message.return_value = None
+        tedee.register_webhook.return_value = 1
+        tedee.delete_webhooks.return_value = None
 
         locks_json = json.loads(load_fixture("locks.json", DOMAIN))
 
diff --git a/tests/components/tedee/test_config_flow.py b/tests/components/tedee/test_config_flow.py
index bc5b73aa4a9f382c506ef8096c69ae3b84a691a8..68a61842fc32a74c66462c91212187ed669065b7 100644
--- a/tests/components/tedee/test_config_flow.py
+++ b/tests/components/tedee/test_config_flow.py
@@ -1,5 +1,5 @@
 """Test the Tedee config flow."""
-from unittest.mock import MagicMock
+from unittest.mock import MagicMock, patch
 
 from pytedee_async import (
     TedeeClientException,
@@ -10,10 +10,12 @@ import pytest
 
 from homeassistant.components.tedee.const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN
 from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
-from homeassistant.const import CONF_HOST
+from homeassistant.const import CONF_HOST, CONF_WEBHOOK_ID
 from homeassistant.core import HomeAssistant
 from homeassistant.data_entry_flow import FlowResultType
 
+from .conftest import WEBHOOK_ID
+
 from tests.common import MockConfigEntry
 
 FLOW_UNIQUE_ID = "112233445566778899"
@@ -22,25 +24,30 @@ LOCAL_ACCESS_TOKEN = "api_token"
 
 async def test_flow(hass: HomeAssistant, mock_tedee: MagicMock) -> None:
     """Test config flow with one bridge."""
-    result = await hass.config_entries.flow.async_init(
-        DOMAIN, context={"source": SOURCE_USER}
-    )
-    await hass.async_block_till_done()
-    assert result["type"] == FlowResultType.FORM
-
-    result2 = await hass.config_entries.flow.async_configure(
-        result["flow_id"],
-        {
+    with patch(
+        "homeassistant.components.tedee.config_flow.webhook_generate_id",
+        return_value=WEBHOOK_ID,
+    ):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_USER}
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == FlowResultType.FORM
+
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            {
+                CONF_HOST: "192.168.1.62",
+                CONF_LOCAL_ACCESS_TOKEN: "token",
+            },
+        )
+
+        assert result2["type"] == FlowResultType.CREATE_ENTRY
+        assert result2["data"] == {
             CONF_HOST: "192.168.1.62",
             CONF_LOCAL_ACCESS_TOKEN: "token",
-        },
-    )
-
-    assert result2["type"] == FlowResultType.CREATE_ENTRY
-    assert result2["data"] == {
-        CONF_HOST: "192.168.1.62",
-        CONF_LOCAL_ACCESS_TOKEN: "token",
-    }
+            CONF_WEBHOOK_ID: WEBHOOK_ID,
+        }
 
 
 async def test_flow_already_configured(
diff --git a/tests/components/tedee/test_init.py b/tests/components/tedee/test_init.py
index ca64c01a9830cf402066ae6151bab9c3f2b62b74..05fb2c1d6ebf5bfa4c5ef368d9080f536dcda819 100644
--- a/tests/components/tedee/test_init.py
+++ b/tests/components/tedee/test_init.py
@@ -1,15 +1,27 @@
 """Test initialization of tedee."""
+from http import HTTPStatus
+from typing import Any
 from unittest.mock import MagicMock
+from urllib.parse import urlparse
 
-from pytedee_async.exception import TedeeAuthException, TedeeClientException
+from pytedee_async.exception import (
+    TedeeAuthException,
+    TedeeClientException,
+    TedeeWebhookException,
+)
 import pytest
 from syrupy import SnapshotAssertion
 
+from homeassistant.components.webhook import async_generate_url
 from homeassistant.config_entries import ConfigEntryState
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers import device_registry as dr
 
+from .conftest import WEBHOOK_ID
+
 from tests.common import MockConfigEntry
+from tests.typing import ClientSessionGenerator
 
 
 async def test_load_unload_config_entry(
@@ -50,6 +62,62 @@ async def test_config_entry_not_ready(
     assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
 
 
+async def test_cleanup_on_shutdown(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_tedee: MagicMock,
+) -> None:
+    """Test the webhook is cleaned up on shutdown."""
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert mock_config_entry.state is ConfigEntryState.LOADED
+
+    hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
+    await hass.async_block_till_done()
+    mock_tedee.delete_webhook.assert_called_once()
+
+
+async def test_webhook_cleanup_errors(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_tedee: MagicMock,
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    """Test the webhook is cleaned up on shutdown."""
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert mock_config_entry.state is ConfigEntryState.LOADED
+
+    mock_tedee.delete_webhook.side_effect = TedeeWebhookException("")
+
+    hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
+    await hass.async_block_till_done()
+    mock_tedee.delete_webhook.assert_called_once()
+    assert "Failed to unregister Tedee webhook from bridge" in caplog.text
+
+
+async def test_webhook_registration_errors(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_tedee: MagicMock,
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    """Test the webhook is cleaned up on shutdown."""
+    mock_tedee.register_webhook.side_effect = TedeeWebhookException("")
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert mock_config_entry.state is ConfigEntryState.LOADED
+
+    mock_tedee.register_webhook.assert_called_once()
+    assert "Failed to register Tedee webhook from bridge" in caplog.text
+
+
 async def test_bridge_device(
     hass: HomeAssistant,
     mock_config_entry: MockConfigEntry,
@@ -67,3 +135,37 @@ async def test_bridge_device(
     )
     assert device
     assert device == snapshot
+
+
+@pytest.mark.parametrize(
+    ("body", "expected_code", "side_effect"),
+    [
+        ({"hello": "world"}, HTTPStatus.OK, None),  # Success
+        (None, HTTPStatus.BAD_REQUEST, None),  # Missing data
+        ({}, HTTPStatus.BAD_REQUEST, TedeeWebhookException),  # Error
+    ],
+)
+async def test_webhook_post(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_tedee: MagicMock,
+    hass_client_no_auth: ClientSessionGenerator,
+    body: dict[str, Any],
+    expected_code: HTTPStatus,
+    side_effect: Exception,
+) -> None:
+    """Test webhook callback."""
+
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    client = await hass_client_no_auth()
+    webhook_url = async_generate_url(hass, WEBHOOK_ID)
+    mock_tedee.parse_webhook_message.side_effect = side_effect
+    resp = await client.post(urlparse(webhook_url).path, json=body)
+
+    # Wait for remaining tasks to complete.
+    await hass.async_block_till_done()
+
+    assert resp.status == expected_code
diff --git a/tests/components/tedee/test_lock.py b/tests/components/tedee/test_lock.py
index fca1ae2b07f13be8f7c0d28ea0f06445792ab862..2f8b1e2b36d0a0e8b46dd1dc6171fe9cebe5aad0 100644
--- a/tests/components/tedee/test_lock.py
+++ b/tests/components/tedee/test_lock.py
@@ -1,9 +1,10 @@
 """Tests for tedee lock."""
 from datetime import timedelta
 from unittest.mock import MagicMock
+from urllib.parse import urlparse
 
 from freezegun.api import FrozenDateTimeFactory
-from pytedee_async import TedeeLock
+from pytedee_async import TedeeLock, TedeeLockState
 from pytedee_async.exception import (
     TedeeClientException,
     TedeeDataUpdateException,
@@ -17,15 +18,21 @@ from homeassistant.components.lock import (
     SERVICE_LOCK,
     SERVICE_OPEN,
     SERVICE_UNLOCK,
+    STATE_LOCKED,
     STATE_LOCKING,
+    STATE_UNLOCKED,
     STATE_UNLOCKING,
 )
+from homeassistant.components.webhook import async_generate_url
 from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
 from homeassistant.core import HomeAssistant
 from homeassistant.exceptions import HomeAssistantError
 from homeassistant.helpers import device_registry as dr, entity_registry as er
 
+from .conftest import WEBHOOK_ID
+
 from tests.common import MockConfigEntry, async_fire_time_changed
+from tests.typing import ClientSessionGenerator
 
 pytestmark = pytest.mark.usefixtures("init_integration")
 
@@ -266,3 +273,28 @@ async def test_new_lock(
     assert state
     state = hass.states.get("lock.lock_6g7h")
     assert state
+
+
+async def test_webhook_update(
+    hass: HomeAssistant,
+    mock_tedee: MagicMock,
+    hass_client_no_auth: ClientSessionGenerator,
+) -> None:
+    """Test updated data set through webhook."""
+
+    state = hass.states.get("lock.lock_1a2b")
+    assert state
+    assert state.state == STATE_UNLOCKED
+
+    webhook_data = {"dummystate": 6}
+    mock_tedee.locks_dict[
+        12345
+    ].state = TedeeLockState.LOCKED  # is updated in the lib, so mock and assert in L296
+    client = await hass_client_no_auth()
+    webhook_url = async_generate_url(hass, WEBHOOK_ID)
+    await client.post(urlparse(webhook_url).path, json=webhook_data)
+    mock_tedee.parse_webhook_message.assert_called_once_with(webhook_data)
+
+    state = hass.states.get("lock.lock_1a2b")
+    assert state
+    assert state.state == STATE_LOCKED