diff --git a/README.md b/README.md
index 5dfb21d3dad99d19d4fa4d1988364d0b7ee0a24a..111b7b4dff68001e10ae80a1d36b40069f379bb2 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,8 @@ $ uvicorn src.app.main:app --reload --reload-dir=./src/app
 3. In the `handler.py` file, you should create a new function that will handle the event. The function should be take the following arguments:
    - body (dict): The body of the request
 4. In the `schema.py` file, you should create 2 new Pydantic models that will be used to validate the body of the request and the response of the handler function.
+   - The first model should be used to validate the body of the request.
+   - The second model should be used to validate the response of the handler function.
 5. In the `__init__.py` file, you must have the following code:
 
 ```python
@@ -147,22 +149,4 @@ handler_function = your_handler_function
 response_model = YourResponseSchema
 ```
 
-6. In the `src/app/api/v1/endpoints/webhook.py` file, you should import the component and add it to the route :
-
-```Python
-from app.components import (
-    your_component,
-)
-
-ResponseModel = Union[
-    ...,
-    your_component.response_model,
-]
-
-@webhook_handler(
-    event_name=your_component.event_name,
-    handler=your_component.handler_function,
-)
-async def webhook_route(webhook_event: BaseWebhookEvent):
-    ...
-```
+6. All the components arle loaded dynamically in the `src/app/api/v1/endpoints/webhook.py` file.
diff --git a/poetry.lock b/poetry.lock
index 1727297e21a7d375278de83ec007ea2a62c899aa..9b8e6de0ad6a039951d0e46002a5a5473d9f4e0a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1009,13 +1009,13 @@ all = ["phonenumbers (>=8,<9)", "pycountry (>=22,<23)"]
 
 [[package]]
 name = "pydantic-settings"
-version = "2.0.1"
+version = "2.0.2"
 description = "Settings management using Pydantic"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pydantic_settings-2.0.1-py3-none-any.whl", hash = "sha256:579bbcbec3501e62bab73867b097ae10218201950e897463c98a182ffe7ed104"},
-    {file = "pydantic_settings-2.0.1.tar.gz", hash = "sha256:f440ec7cfb6dc63f03226c47b0e7803750d1b66a49ed944ac23eb4f0c84f8722"},
+    {file = "pydantic_settings-2.0.2-py3-none-any.whl", hash = "sha256:6183a2abeab465d5a3ab69758e9a22d38b0cc2ba193f0b85f6971a252ea630f6"},
+    {file = "pydantic_settings-2.0.2.tar.gz", hash = "sha256:342337fff50b23585e807a86dec85037900972364435c55c2fc00d16ff080539"},
 ]
 
 [package.dependencies]
@@ -1077,12 +1077,12 @@ pylint = ">=1.7"
 
 [[package]]
 name = "pylint-pydantic"
-version = "0.2.0"
+version = "0.2.2"
 description = "A Pylint plugin to help Pylint understand the Pydantic"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pylint_pydantic-0.2.0-py3-none-any.whl", hash = "sha256:f44e38239c0da3cd1f500f7382ce57aaceb23200c2a675367d3e34b458b597a3"},
+    {file = "pylint_pydantic-0.2.2-py3-none-any.whl", hash = "sha256:37f9b16b68f258dcb638efd3a78632e237c80d6cde036fe73514de9cdf11aff2"},
 ]
 
 [package.dependencies]
@@ -1689,4 +1689,4 @@ files = [
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.10"
-content-hash = "dd6abd3eff19e9e54a564ceea8147ab86abcf378fb56d5138aeaf0e68349a633"
+content-hash = "cfdd00d4f2c9c54ea7d9afafab03f7f62b143b14257e6837604dec2afbbbf95e"
diff --git a/pyproject.toml b/pyproject.toml
index 7acc1de3a5f37306e7c3b6b4f4bdcd9d0efe6bc9..cdfdb14f996e2b10b3d671287cb6c37e82546568 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "zoom-python-webhook"
-version = "0.1.0"
+version = "0.1.1"
 description = "Fast API app for Zoom Webhook"
 authors = ["Samuel Guillemet <samuel.guillemet@telecom-sudparis.eu>"]
 readme = "README.md"
@@ -9,7 +9,7 @@ packages = [{include = "app", from = "src"}]
 [tool.poetry.dependencies]
 python = "^3.10"
 fastapi = {extras = ["all"], version = "^0.100.0"}
-pydantic-settings = "^2.0.1"
+pydantic-settings = "^2.0.2"
 pytz = "^2023.3"
 
 
@@ -23,7 +23,7 @@ mypy-extensions = "^1.0.0"
 pylint = "^2.17.4"
 pytest = "^7.4.0"
 pytest-cov = "^4.1.0"
-pylint-pydantic = "^0.2.0"
+pylint-pydantic = "^0.2.2"
 pytest-env = "^0.8.2"
 pytest-asyncio = "^0.21.1"
 httpx = "^0.24.1"
diff --git a/src/app/api/v1/endpoints/webhook.py b/src/app/api/v1/endpoints/webhook.py
index 4429ec16b3679da26491897b27b0b78ded6aade7..e9369b0f5dd4d3c65b4e0960c4eb0eb4a3b2e207 100644
--- a/src/app/api/v1/endpoints/webhook.py
+++ b/src/app/api/v1/endpoints/webhook.py
@@ -1,18 +1,18 @@
 """ Webhook endpoint. """
 
 import logging
-from typing import Union
+from typing import Callable, Union
 
 from fastapi import APIRouter, Depends, HTTPException
 
-from app.components import (
-    endpoint_url_validation,
-    zoom_room_checked_in,
-    zoom_room_checked_out,
+from app import components
+from app.core.base_webhook_event_schema import (
+    BaseResponseWebhookEvent,
+    BaseWebhookEvent,
+    ErrorModel,
 )
-from app.core.base_webhook_event_schema import BaseWebhookEvent, ErrorModel
-from app.core.decorators import webhook_handler
 from app.core.dependencies import verify_webhook_signature
+from app.utils.load_submodules import load_submodules
 
 logger = logging.getLogger("app.api.v1.webhook")
 
@@ -24,32 +24,28 @@ responses = {
     501: {"description": "Webhook event not supported.", "model": ErrorModel},
 }
 
-# Union of all response models for the webhook handlers.
-ResponseModel = Union[
-    endpoint_url_validation.response_model,
-    zoom_room_checked_in.response_model,
-    zoom_room_checked_out.response_model,
+# Load all webhook components and create a list of tuples containing
+# the event name, handler function, and response model.
+components_tuple: list[
+    tuple[str, Callable[..., BaseResponseWebhookEvent], BaseResponseWebhookEvent]
+] = [
+    (
+        getattr(component, "event_name"),
+        getattr(component, "handler_function"),
+        getattr(component, "response_model"),
+    )
+    for component in load_submodules(components)
 ]
 
 
 @router.post(
     "",  # Will be /webhook
     dependencies=[Depends(verify_webhook_signature)],
-    response_model=ResponseModel,
+    response_model=Union[
+        tuple(response_model for _, _, response_model in components_tuple)  # type: ignore
+    ],
     responses={**responses},
 )
-@webhook_handler(
-    event_name=endpoint_url_validation.event_name,
-    handler_function=endpoint_url_validation.handler_function,
-)
-@webhook_handler(
-    event_name=zoom_room_checked_in.event_name,
-    handler_function=zoom_room_checked_in.handler_function,
-)
-@webhook_handler(
-    event_name=zoom_room_checked_out.event_name,
-    handler_function=zoom_room_checked_out.handler_function,
-)
 async def webhook_route(webhook_event: BaseWebhookEvent):
     """
     Handle incoming webhook events.
@@ -60,6 +56,10 @@ async def webhook_route(webhook_event: BaseWebhookEvent):
     Raises:
         HTTPException: If the webhook event is not supported.
     """
+    for event_name, handler_function, _ in components_tuple:
+        if webhook_event.event == event_name:
+            return handler_function(webhook_event.model_dump())
+
     logger.warning("Unhandled webhook event: %s", webhook_event.event)
     raise HTTPException(
         detail=f"Webhook event {webhook_event.event} not supported.",
diff --git a/src/app/core/decorators.py b/src/app/core/decorators.py
deleted file mode 100644
index fce073c1e33b99745940683792a6c39d314de10d..0000000000000000000000000000000000000000
--- a/src/app/core/decorators.py
+++ /dev/null
@@ -1,34 +0,0 @@
-""" This module contains decorators for the webhook handlers. """
-
-from typing import Awaitable, Callable
-
-from app.core.base_webhook_event_schema import (
-    BaseResponseWebhookEvent,
-    BaseWebhookEvent,
-)
-
-
-def webhook_handler(
-    event_name: str, handler_function: Callable[[dict], BaseResponseWebhookEvent]
-) -> Callable:
-    """This function is a decorator that registers a webhook handler.
-
-    Args:
-        event_name (str): The name of the webhook event.
-        handler_function (Callable): The handler function for the webhook event.
-
-    Returns:
-        Callable: The decorator will call either the handler function for the webhook event,
-        or the base function if the event name does not match.
-    """
-
-    def decorator(func: Callable[..., Awaitable]) -> Callable:
-        async def wrapper(webhook_event: BaseWebhookEvent):
-            if webhook_event.event != event_name:
-                return await func(webhook_event)
-
-            return handler_function(webhook_event.model_dump())
-
-        return wrapper
-
-    return decorator
diff --git a/src/app/utils/load_submodules.py b/src/app/utils/load_submodules.py
new file mode 100644
index 0000000000000000000000000000000000000000..6570b9078db2c8edf64eae5749140cf8a43fe032
--- /dev/null
+++ b/src/app/utils/load_submodules.py
@@ -0,0 +1,13 @@
+import importlib
+import pkgutil
+from types import ModuleType
+
+
+def load_submodules(parent_module: ModuleType) -> list[ModuleType]:
+    """Load all submodules of a given module."""
+    submodules: list[ModuleType] = []
+    for _, submodule_name, _ in pkgutil.iter_modules(parent_module.__path__):
+        submodule_path = f"{parent_module.__name__}.{submodule_name}"
+        submodule = importlib.import_module(submodule_path)
+        submodules.append(submodule)
+    return submodules
diff --git a/tests/app/core/test_decorators.py b/tests/app/core/test_decorators.py
deleted file mode 100644
index 3684d43d5bee9055434c11e8dcdfd3e71b95f280..0000000000000000000000000000000000000000
--- a/tests/app/core/test_decorators.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# pylint: disable=unused-argument
-from unittest.mock import Mock
-
-import pytest
-
-from app.core.base_webhook_event_schema import BaseWebhookEvent
-from app.core.decorators import webhook_handler
-
-webhook_event_test = BaseWebhookEvent(
-    payload={"plainToken": "q9ibPhGeRZ6ayx5WTrXjRw"},
-    event_ts=1689061099652,
-    event="event.test.1",
-)
-
-
-@pytest.mark.asyncio
-async def test_webhook_handler():
-    mock_handler = Mock()
-
-    @webhook_handler(
-        event_name="event.test.1",
-        handler_function=mock_handler,
-    )
-    async def test_func(webhook_event: BaseWebhookEvent):
-        assert False  # This should not be called.
-
-    await test_func(webhook_event_test)
-
-    assert mock_handler.called
-    assert mock_handler.call_args[0][0]["payload"] == webhook_event_test.payload
-    assert mock_handler.call_args[0][0]["event_ts"] == webhook_event_test.event_ts
-    assert mock_handler.call_args[0][0]["event"] == webhook_event_test.event
-
-
-@pytest.mark.asyncio
-async def test_webhook_handler_not_event():
-    mock_handler = Mock()
-
-    @webhook_handler(
-        event_name="wrong",
-        handler_function=mock_handler,
-    )
-    async def test_func(webhook_event: BaseWebhookEvent) -> BaseWebhookEvent:
-        return webhook_event
-
-    result = await test_func(webhook_event_test)
-
-    assert result == webhook_event_test
-    assert not mock_handler.called
-
-
-@pytest.mark.asyncio
-async def test_webhook_handler_multiple_events_0():
-    mock_handler_1 = Mock()
-    mock_handler_2 = Mock()
-
-    @webhook_handler(
-        event_name="event.test.1",
-        handler_function=mock_handler_1,
-    )
-    @webhook_handler(
-        event_name="event.test.2",
-        handler_function=mock_handler_2,
-    )
-    async def test_func(webhook_event: BaseWebhookEvent):
-        assert False
-
-    await test_func(webhook_event_test)
-
-    assert mock_handler_1.called
-    assert not mock_handler_2.called
-
-
-@pytest.mark.asyncio
-async def test_webhook_handler_multiple_events_1():
-    mock_handler_1 = Mock()
-    mock_handler_2 = Mock()
-
-    @webhook_handler(
-        event_name="event.test.2",
-        handler_function=mock_handler_2,
-    )
-    @webhook_handler(
-        event_name="event.test.1",
-        handler_function=mock_handler_1,
-    )
-    async def test_func(webhook_event: BaseWebhookEvent):
-        assert False
-
-    await test_func(webhook_event_test)
-
-    assert mock_handler_1.called
-    assert not mock_handler_2.called
diff --git a/tests/app/utils/__init__.py b/tests/app/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/app/utils/test_load_submodules.py b/tests/app/utils/test_load_submodules.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ca6045ce799f5413cb24844b197d0a53b07ee44
--- /dev/null
+++ b/tests/app/utils/test_load_submodules.py
@@ -0,0 +1,33 @@
+from types import ModuleType
+from unittest.mock import MagicMock, patch
+
+from app.utils.load_submodules import load_submodules
+
+
+def test_load_submodules():
+    parent_module = MagicMock(spec=ModuleType)
+    parent_module.__name__ = "parent_module"
+    parent_module.__path__ = ["path/to/parent_module"]
+
+    mock_submodule1 = MagicMock(spec=ModuleType)
+    mock_submodule2 = MagicMock(spec=ModuleType)
+
+    mock_iter_modules = MagicMock()
+    mock_iter_modules.return_value = [
+        ("path/to/parent_module/submodule1", "submodule1", True),
+        ("path/to/parent_module/submodule2", "submodule2", True),
+    ]
+
+    mock_import_module = MagicMock()
+    mock_import_module.side_effect = [mock_submodule1, mock_submodule2]
+
+    with patch("app.utils.load_submodules.pkgutil.iter_modules", mock_iter_modules):
+        with patch(
+            "app.utils.load_submodules.importlib.import_module", mock_import_module
+        ):
+            submodules = load_submodules(parent_module)
+
+    assert submodules == [mock_submodule1, mock_submodule2]
+    mock_iter_modules.assert_called_once_with(parent_module.__path__)
+    mock_import_module.assert_any_call("parent_module.submodule1")
+    mock_import_module.assert_any_call("parent_module.submodule2")