BerriAI/litellm · error · ValueError

Invalid webhook url value for

Error message

Invalid webhook url value for: {webhook_urls}. Got type={type(_env_value)}

What it means

LiteLLM's Slack alerting lets you reference webhook URLs indirectly via the special 'os.environ/ENV_VAR_NAME' syntax in `alert_to_webhook_url`. At startup, `process_slack_alerting_variables` resolves each such reference through `get_secret` and requires the result to be a plain string. If the secret manager returns None (var not set) or a non-string (e.g. a dict from a secret backend), this ValueError is raised. The message reports the offending reference and the actual Python type it got.

Solutions

  1. Verify the env var actually exists and holds a string: run `echo $SLACK_WEBHOOK_URL_1` (or print `os.getenv('SLACK_WEBHOOK_URL_1')`) in the same environment where litellm runs
  2. Fix the reference spelling so the name after 'os.environ/' exactly matches the exported variable name
  3. If the value lives in a secret manager, ensure it is stored as a plain string secret, not a JSON blob/key-value payload
  4. As a quick unblock, replace the 'os.environ/...' reference with the literal webhook URL to confirm the rest of the alerting config is correct

Example fix

# before
alert_to_webhook_url = {
    "llm_api_latency_alert": "os.environ/SLACK_WEBHOOK_URL",  # var not exported -> resolves to None
}
litellm.alerting=["slack"]

# after
# export SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/XXXX first, then
alert_to_webhook_url = {
    "llm_api_latency_alert": "os.environ/SLACK_WEBHOOK_URL",
}
Defensive patterns

Strategy: validation

Validate before calling

import os

def webhook_refs_ok(alert_to_webhook_url: dict) -> None:
    for alert_type, urls in alert_to_webhook_url.items():
        candidates = urls if isinstance(urls, list) else [urls]
        for u in candidates:
            if isinstance(u, str) and u.startswith("os.environ/"):
                name = u.split("os.environ/", 1)[1]
                val = os.getenv(name)
                if not isinstance(val, str) or not val:
                    raise SystemExit(f"Missing env var {name} for Slack webhook ({alert_type})")

webhook_refs_ok({"llm_api_latency_alert": "os.environ/SLACK_WEBHOOK_URL_1"})

Type guard

def is_resolvable_webhook_ref(ref: str) -> bool:
    import os
    if not isinstance(ref, str):
        return False
    if ref.startswith("os.environ/"):
        return isinstance(os.getenv(ref.split("os.environ/", 1)[1]), str)
    return True

Try / catch

try:
    litellm.alerting = ["slack"]  # triggers webhook resolution
except ValueError as e:
    if "Invalid webhook url value" in str(e):
        log.error("Slack webhook env var unresolved", extra={"detail": str(e)})
        # degrade: disable alerting rather than crash the app
        litellm.alerting = None
    else:
        raise

Prevention

When it happens

Trigger: Calling `litellm.alerting()` or constructing SlackAlerting with `alert_to_webhook_url={'llm_api_latency_alert': 'os.environ/SLACK_WEBHOOK_URL_1'}` when SLACK_WEBHOOK_URL_1 is unset, or when the secret is stored in a connected secret manager (e.g. AWS/Kubernetes) under a non-string value. Also triggered with a list of URLs where one element is an 'os.environ/...' reference that resolves to None.

Common situations: Typo in the env var name after 'os.environ/'; forgetting to export the var in the deployment shell/docker-compose/k8s manifest; secret manager returning a JSON object instead of a raw string; CI environments where the webhook var is only set in one job.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/6f1e4bcec05f6184. Report an issue: GitHub.

Appendix: source

Thrown at litellm/integrations/SlackAlerting/utils.py:48

    for alert_type, webhook_urls in alert_to_webhook_url.items():
        if isinstance(webhook_urls, list):
            _webhook_values: list[str] = []
            for webhook_url in webhook_urls:
                if "os.environ/" in webhook_url:
                    _env_value = get_secret(secret_name=webhook_url)
                    if not isinstance(_env_value, str):
                        raise ValueError(f"Invalid webhook url value for: {webhook_url}. Got type={type(_env_value)}")
                    _webhook_values.append(_env_value)
                else:
                    _webhook_values.append(webhook_url)

            alert_to_webhook_url[alert_type] = _webhook_values
        else:
            _webhook_value_str: str = webhook_urls
            if "os.environ/" in webhook_urls:
                _env_value = get_secret(secret_name=webhook_urls)
                if not isinstance(_env_value, str):
                    raise ValueError(f"Invalid webhook url value for: {webhook_urls}. Got type={type(_env_value)}")
                _webhook_value_str = _env_value
            else:
                _webhook_value_str = webhook_urls

            alert_to_webhook_url[alert_type] = _webhook_value_str

    return alert_to_webhook_url


async def _add_langfuse_trace_id_to_alert(
    request_data: dict | None = None,
) -> str | None:
    """
    Returns langfuse trace url

    - check:
    -> existing_trace_id
    -> trace_id

View on GitHub (pinned to 77b7c6c40c)