diff --git a/Changelog.md b/Changelog.md
index c33bdc46ccddf0b7944387da9bff0a005bfa1c4e..e3ca355c2d75017e766a7c932278c0dd33e1b7e2 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,9 @@
 
 All notable changes to this project will be documented in this file.
 
+## [0.3] - 2024-01-26
+- Added Token authentication for LSO callbacks.
+
 ## [0.3] - 2024-01-23
 - Fixed related to the Authentication and some small improvments.
 
diff --git a/gso/api/v1/subscriptions.py b/gso/api/v1/subscriptions.py
index 24c9307f1ce67371ea3f89c6f0888380ad7ea09c..9cc6075ac8bb4858e0354afb691b645a0bdcc1dd 100644
--- a/gso/api/v1/subscriptions.py
+++ b/gso/api/v1/subscriptions.py
@@ -8,13 +8,13 @@ from orchestrator.domain import SubscriptionModel
 from orchestrator.schemas import SubscriptionDomainModelSchema
 from orchestrator.services.subscriptions import build_extended_domain_model
 
-from gso.auth.security import opa_security_default
+from gso.auth.api_key_auth import get_api_key
 from gso.services.subscriptions import get_active_router_subscriptions
 
 router = APIRouter(
     prefix="/subscriptions",
     tags=["Subscriptions"],
-    dependencies=[Depends(opa_security_default)],
+    dependencies=[Depends(get_api_key)],
 )
 
 
diff --git a/gso/auth/api_key_auth.py b/gso/auth/api_key_auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..f66e7b8535e553d49a060528fc024bde15222be6
--- /dev/null
+++ b/gso/auth/api_key_auth.py
@@ -0,0 +1,22 @@
+"""Manage API key validation for FastAPI routes."""
+
+from fastapi import Depends, HTTPException, status
+from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+
+from gso.settings import load_oss_params
+
+security = HTTPBearer()
+
+
+async def get_api_key(
+    credentials: HTTPAuthorizationCredentials = Depends(security),  # noqa: B008
+) -> str:
+    """Validate the provided API key against known third-party keys and returns it if valid, else raises HTTP 403."""
+    settings = load_oss_params()
+    api_key = credentials.credentials
+
+    # TODO: This is a simulated database of API keys which should be replace with a real one
+    if api_key in settings.THIRD_PARTY_API_KEYS.values():
+        return api_key
+
+    raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid API Key")
diff --git a/gso/oss-params-example.json b/gso/oss-params-example.json
index fa8616e4151a50175db13e52b48ed28513861133..931367ea48746610b29646651e99a68f89d1288e 100644
--- a/gso/oss-params-example.json
+++ b/gso/oss-params-example.json
@@ -73,5 +73,9 @@
     "broker_url": "redis://localhost:6379/0",
     "result_backend": "rpc://localhost:6379/0",
     "result_expires": 3600
+  },
+  "THIRD_PARTY_API_KEYS": {
+    "AnsibleDynamicInventoryGenerator": "REALLY_random_AND_secure_T0keN",
+    "Application_2": "another_REALY_random_AND_3cure_T0keN"
   }
 }
diff --git a/gso/services/provisioning_proxy.py b/gso/services/provisioning_proxy.py
index d93bbb7220a718ce72d40a97cc7a6711b50c27cd..7aba02a4c01b93c476773c0d56f4d86445ce2cf0 100644
--- a/gso/services/provisioning_proxy.py
+++ b/gso/services/provisioning_proxy.py
@@ -62,8 +62,8 @@ def execute_playbook(
     .. code-block:: json
 
         "inventory": {
-            "_meta": {
-                "vars": {
+            "all": {
+                "hosts": {
                     "host1.local": {
                         "foo": "bar"
                     },
@@ -71,12 +71,6 @@ def execute_playbook(
                         "key": "value"
                     }
                 }
-            },
-            "all": {
-                "hosts": {
-                    "host1.local": null,
-                    "host2.local": null
-                }
             }
         }
 
diff --git a/gso/settings.py b/gso/settings.py
index f05632ed3968d8f727c6d108618c0c80a5065ca8..ca0da591c00e829585db55c5a68731b3c44aac90 100644
--- a/gso/settings.py
+++ b/gso/settings.py
@@ -161,6 +161,7 @@ class OSSParams(BaseSettings):
     MONITORING: MonitoringParams
     PROVISIONING_PROXY: ProvisioningProxyParams
     CELERY: CeleryParams
+    THIRD_PARTY_API_KEYS: dict[str, str]
 
 
 def load_oss_params() -> OSSParams:
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
index 32353fcf12856cf73e76b2ad16d6a8e87c72d4ad..d2f6d8d66b4dcd95250c6bcbc87f15815f3d15a6 100644
--- a/gso/workflows/router/update_ibgp_mesh.py
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -59,23 +59,22 @@ def calculate_pe_router_list() -> State:
 def _generate_pe_inventory(pe_router_list: list[Router]) -> dict[str, Any]:
     """Generate an Ansible-compatible inventory for executing playbooks. Contains all active PE routers."""
     return {
-        "_meta": {
-            "vars": {
+        "all": {
+            "hosts": {
                 router.router.router_fqdn: {
                     "lo4": str(router.router.router_lo_ipv4_address),
                     "lo6": str(router.router.router_lo_ipv6_address),
-                    "vendor": router.router.vendor,
+                    "vendor": str(router.router.vendor),
                 }
                 for router in pe_router_list
             }
         },
-        "all": {"hosts": {router.router.router_fqdn: None for router in pe_router_list}},
     }
 
 
 @step("[DRY RUN] Add P router to iBGP mesh")
 def add_p_to_mesh_dry(
-    subscription: Router, callback_route: str, pe_router_list: list[Router], tt_number: str, process_id: UUIDstr
+    subscription: dict[str, Any], callback_route: str, pe_router_list: list[Router], tt_number: str, process_id: UUIDstr
 ) -> None:
     """Perform a dry run of adding the new P router to the PE router mesh."""
     extra_vars = {
@@ -95,7 +94,7 @@ def add_p_to_mesh_dry(
 
 @step("[FOR REAL] Add P router to iBGP mesh")
 def add_p_to_mesh_real(
-    subscription: Router, callback_route: str, pe_router_list: list[Router], tt_number: str, process_id: UUIDstr
+    subscription: dict[str, Any], callback_route: str, pe_router_list: list[Router], tt_number: str, process_id: UUIDstr
 ) -> None:
     """Add the P router to the mesh of PE routers."""
     extra_vars = {
@@ -115,7 +114,7 @@ def add_p_to_mesh_real(
 
 @step("[DRY RUN] Add all PE routers to P router iBGP table")
 def add_all_pe_to_p_dry(
-    subscription: Router, pe_router_list: list[Router], callback_route: str, tt_number: str, process_id: UUIDstr
+    subscription: dict[str, Any], pe_router_list: list[Router], callback_route: str, tt_number: str, process_id: UUIDstr
 ) -> None:
     """Perform a dry run of adding the list of all PE routers to the new P router."""
     extra_vars = {
@@ -136,14 +135,14 @@ def add_all_pe_to_p_dry(
     provisioning_proxy.execute_playbook(
         playbook_name="update_ibgp_mesh.yaml",
         callback_route=callback_route,
-        inventory=subscription.router.router_fqdn,
+        inventory=subscription["router"]["router_fqdn"],
         extra_vars=extra_vars,
     )
 
 
 @step("[FOR REAL] Add all PE routers to P router iBGP table")
 def add_all_pe_to_p_real(
-    subscription: Router, pe_router_list: list[Router], callback_route: str, tt_number: str, process_id: UUIDstr
+    subscription: dict[str, Any], pe_router_list: list[Router], callback_route: str, tt_number: str, process_id: UUIDstr
 ) -> None:
     """Add the list of all PE routers to the new P router."""
     extra_vars = {
@@ -164,7 +163,7 @@ def add_all_pe_to_p_real(
     provisioning_proxy.execute_playbook(
         playbook_name="update_ibgp_mesh.yaml",
         callback_route=callback_route,
-        inventory=subscription.router.router_fqdn,
+        inventory=subscription["router"]["router_fqdn"],
         extra_vars=extra_vars,
     )
 
diff --git a/setup.py b/setup.py
index ecb402620217e9355290ad5f0ff3d325bffe7faa..653131fdc8e3c5cea12e2958117e21125a4994c2 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="geant-service-orchestrator",
-    version="0.3",
+    version="0.4",
     author="GÉANT",
     author_email="swd@geant.org",
     description="GÉANT Service Orchestrator",
diff --git a/test/api/test_subscriptions.py b/test/api/test_subscriptions.py
index d56d2d582c21a89aa255e417a19dc04b38bff6e4..b4bad4e091301ccd644a417f9cf7391779d1d32a 100644
--- a/test/api/test_subscriptions.py
+++ b/test/api/test_subscriptions.py
@@ -3,14 +3,30 @@ from orchestrator.types import SubscriptionLifecycle
 ROUTER_SUBSCRIPTION_ENDPOINT = "/api/v1/subscriptions/routers"
 
 
-def test_router_subscriptions_endpoint(test_client, nokia_router_subscription_factory):
+def test_router_subscriptions_endpoint_with_valid_api_key(test_client, nokia_router_subscription_factory):
     nokia_router_subscription_factory()
     nokia_router_subscription_factory()
     nokia_router_subscription_factory()
     nokia_router_subscription_factory(status=SubscriptionLifecycle.TERMINATED)
     nokia_router_subscription_factory(status=SubscriptionLifecycle.INITIAL)
 
-    response = test_client.get(ROUTER_SUBSCRIPTION_ENDPOINT)
+    response = test_client.get(
+        ROUTER_SUBSCRIPTION_ENDPOINT, headers={"Authorization": "Bearer REALY_random_AND_3cure_T0keN"}
+    )
 
     assert response.status_code == 200
     assert len(response.json()) == 3
+
+
+def test_router_subscriptions_endpoint_with_invalid_api_key(test_client, nokia_router_subscription_factory):
+    response = test_client.get(ROUTER_SUBSCRIPTION_ENDPOINT, headers={"Authorization": "Bearer fake_invalid_api_key"})
+
+    assert response.status_code == 403
+    assert response.json() == {"detail": "Invalid API Key"}
+
+
+def test_router_subscriptions_endpoint_without_api_key(test_client, nokia_router_subscription_factory):
+    response = test_client.get(ROUTER_SUBSCRIPTION_ENDPOINT)
+
+    assert response.status_code == 403
+    assert response.json() == {"detail": "Not authenticated"}
diff --git a/test/conftest.py b/test/conftest.py
index edb1dffba401f2f57b5c140d171570468c7ee8e7..dd6e9d6d3fc3429f0ce0df33213de8dc1f195523 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -204,6 +204,10 @@ def configuration_data() -> dict:
                 "result_backend": "rpc://localhost:6379/0",
                 "result_expires": 3600,
             },
+            "THIRD_PARTY_API_KEYS": {
+                "AnsibleDynamicInventoryGenerator": "REALY_random_AND_3cure_T0keN",
+                "Application_2": "another_REALY_random_AND_3cure_T0keN",
+            },
         }