From f04337030ade4d22044004c6087da2f7a8f41556 Mon Sep 17 00:00:00 2001 From: Will Date: Fri, 10 Jul 2026 15:58:54 +0100 Subject: [PATCH] fix(health): return structured response from webhook health check branch The service == "webhook" branch of health_services_endpoint() called proxy_logging_obj.budget_alerts() but had no return statement, so execution fell through to the end of the function and implicitly returned None. FastAPI serialized this as JSON `null` with HTTP 200, unlike every other service branch which returns a structured {"status": ..., "message": ...} dict. Add the missing return so the webhook branch matches the response shape used by the other branches, plus a regression test that mocks budget_alerts and asserts both the call and the structured response. --- .../health_endpoints/_health_endpoints.py | 1 + .../health_endpoints/test_health_endpoints.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index 4250b9668ff..8232617bd15 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -332,6 +332,7 @@ async def health_services_endpoint( type="user_budget", user_info=user_info, ) + return {"status": "success", "message": "Mock webhook alert sent"} elif service == "sqs": from litellm.integrations.sqs import SQSLogger diff --git a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py index 917bedcb93f..bb182db9611 100644 --- a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py +++ b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py @@ -265,6 +265,36 @@ async def test_health_services_endpoint_sqs(status, error_message): mock_instance.async_health_check.assert_awaited_once() +@pytest.mark.asyncio +async def test_health_services_endpoint_webhook_returns_structured_response(): + """ + Regression test: the /health/services webhook branch called + proxy_logging_obj.budget_alerts() but had no return statement, so + execution fell through to the end of the function and returned None + (serialized as JSON null with HTTP 200) instead of a structured + {"status": ..., "message": ...} dict like every other service branch. + """ + user_api_key_dict = UserAPIKeyAuth( + token="test-token", + user_id="test-user", + key_alias="test-key-alias", + team_id="test-team", + ) + mock_budget_alerts = AsyncMock() + + with patch( + "litellm.proxy.proxy_server.proxy_logging_obj.budget_alerts", + mock_budget_alerts, + ): + result = await health_services_endpoint( + user_api_key_dict=user_api_key_dict, + service="webhook", + ) + + mock_budget_alerts.assert_awaited_once() + assert result == {"status": "success", "message": "Mock webhook alert sent"} + + @pytest.mark.asyncio async def test_health_license_endpoint_with_active_license(): license_data = {