Compare commits

...

1 Commits

Author SHA1 Message Date
lebaudantoine 8853cbf2ae 🐛(backend) acknowledge unknown LiveKit webhook events instead of 422
Around 0.76% of incoming LiveKit webhooks were being flagged as
unprocessable and returned a 422, even though LiveKit was sending
legitimate data — just with event types we do not handle. This
inflated error metrics and made real webhook issues harder to spot.

Return a 200 for these webhooks instead. When a new, unhandled
event type shows up, log a warning so we can decide whether it is
worth adding explicit handling.
2026-09-09 11:44:15 +02:00
4 changed files with 25 additions and 24 deletions
+1
View File
@@ -10,6 +10,7 @@ and this project adheres to
### Fixed ### Fixed
- 🐛(backend) acknowledge unknown LiveKit webhook events instead of 422
- 🔒️(backend) enforce display name setting on rename API - 🔒️(backend) enforce display name setting on rename API
## [1.31.0] - 2026-09-08 ## [1.31.0] - 2026-09-08
+8 -10
View File
@@ -52,12 +52,6 @@ class InvalidPayloadError(LiveKitWebhookError):
status_code = 400 status_code = 400
class UnsupportedEventTypeError(LiveKitWebhookError):
"""Unsupported event type."""
status_code = 422
class ActionFailedError(LiveKitWebhookError): class ActionFailedError(LiveKitWebhookError):
"""Webhook action fails to process or complete.""" """Webhook action fails to process or complete."""
@@ -74,6 +68,7 @@ class LiveKitWebhookEventType(Enum):
# Participant events # Participant events
PARTICIPANT_JOINED = "participant_joined" PARTICIPANT_JOINED = "participant_joined"
PARTICIPANT_LEFT = "participant_left" PARTICIPANT_LEFT = "participant_left"
PARTICIPANT_CONNECTION_ABORTED = "participant_connection_aborted"
# Track events # Track events
TRACK_PUBLISHED = "track_published" TRACK_PUBLISHED = "track_published"
@@ -153,10 +148,13 @@ class LiveKitEventsService:
try: try:
webhook_type = LiveKitWebhookEventType(data.event) webhook_type = LiveKitWebhookEventType(data.event)
except ValueError as e: except ValueError:
raise UnsupportedEventTypeError( logger.warning(
f"Unknown webhook type: {data.event}" "Ignoring unknown LiveKit webhook event type '%s' for room '%s'",
) from e data.event,
room_name,
)
return
# Handle according to received webhook type # Handle according to received webhook type
handler = self._webhook_handlers.get(webhook_type.value) handler = self._webhook_handlers.get(webhook_type.value)
@@ -94,7 +94,7 @@ def test_invalid_payload(client, auth_token, mock_livekit_config):
def test_unknown_event_type(client, mock_livekit_config): def test_unknown_event_type(client, mock_livekit_config):
"""Should return 422 for unknown event type.""" """Should acknowledge (200) an unknown event type rather than reject it."""
event_data = json.dumps({"event": "unknown_event_type"}) event_data = json.dumps({"event": "unknown_event_type"})
# Generate auth token for this specific payload # Generate auth token for this specific payload
@@ -112,10 +112,8 @@ def test_unknown_event_type(client, mock_livekit_config):
HTTP_AUTHORIZATION=auth_token, HTTP_AUTHORIZATION=auth_token,
) )
assert response.status_code == 422 assert response.status_code == 200
assert response.json() == { assert response.json() == {"status": "success"}
"status": "error",
}
@mock.patch.object(LiveKitEventsService, "_handle_room_finished") @mock.patch.object(LiveKitEventsService, "_handle_room_finished")
@@ -16,7 +16,6 @@ from core.services.livekit_events import (
AuthenticationError, AuthenticationError,
InvalidPayloadError, InvalidPayloadError,
LiveKitEventsService, LiveKitEventsService,
UnsupportedEventTypeError,
api, api,
) )
from core.services.lobby import LobbyService from core.services.lobby import LobbyService
@@ -665,22 +664,27 @@ def test_receive_missing_auth(service):
@mock.patch.object(api.WebhookReceiver, "receive") @mock.patch.object(api.WebhookReceiver, "receive")
def test_receive_unsupported_event(mock_receive, service): def test_receive_unknown_event_is_acknowledged(mock_receive, service, caplog):
"""Should raise LiveKitWebhookError for unsupported events.""" """Unknown event types are logged and ignored, not rejected.
LiveKit adds event types over time and does not retry 4xx responses, so
raising here would silently drop the event.
"""
mock_request = mock.MagicMock() mock_request = mock.MagicMock()
mock_request.headers = {"Authorization": "test_token"} mock_request.headers = {"Authorization": "test_token"}
mock_request.body = b"{}" mock_request.body = b"{}"
# Mock returned data with unsupported event type
mock_data = mock.MagicMock() mock_data = mock.MagicMock()
mock_data.room.name = str(uuid.uuid4()) mock_data.room.name = str(uuid.uuid4())
mock_data.event = "unsupported_event" mock_data.event = "some_future_event"
mock_receive.return_value = mock_data mock_receive.return_value = mock_data
with pytest.raises( with caplog.at_level("WARNING", logger="core.services.livekit_events"):
UnsupportedEventTypeError, match="Unknown webhook type: unsupported_event" service.receive(mock_request) # must not raise
):
service.receive(mock_request) assert "Ignoring unknown LiveKit webhook event type 'some_future_event'" in (
caplog.text
)
@mock.patch.object(api.WebhookReceiver, "receive") @mock.patch.object(api.WebhookReceiver, "receive")