BerriAI/litellm · error · Exception

Invalid hash key. Hash key={hashed_token}. Decrypted token={

Error message

Invalid hash key. Hash key={hashed_token}. Decrypted token={decrypted_token}. Error: {e}

What it means

Raised when decrypting a UI session hash token: decrypt_value_helper succeeded, but json.loads(...) or UserAPIKeyAuth.model_validate(...) failed on the decrypted payload. The token was encrypted with the proxy's ui_hash_key; if the key changed between sessions or the payload is corrupt, decryption/validation produces garbage and this Exception names the hash key, the decrypted token, and the underlying parse error.

Source

Thrown at litellm/proxy/auth/auth_checks.py:2997

    @staticmethod
    def get_key_object_from_ui_hash_key(
        hashed_token: str,
    ) -> UserAPIKeyAuth | None:
        import json

        from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth
        from litellm.proxy.common_utils.encrypt_decrypt_utils import (
            decrypt_value_helper,
        )

        decrypted_token: Final = decrypt_value_helper(hashed_token, key="ui_hash_key", exception_type="debug")
        if decrypted_token is None:
            return None
        try:
            return UserAPIKeyAuth.model_validate(json.loads(decrypted_token))
        except Exception as e:
            raise Exception(f"Invalid hash key. Hash key={hashed_token}. Decrypted token={decrypted_token}. Error: {e}")


async def _fetch_key_object_from_db_with_reconnect(
    hashed_token: str,
    prisma_client: PrismaClient,
    parent_otel_span: Span | None,
    proxy_logging_obj: ProxyLogging | None,
) -> BaseModel | None:
    """
    Fetch key object from DB and retry once if a DB connection error can be healed.
    """
    try:
        return await prisma_client.get_data(
            token=hashed_token,
            table_name="combined_view",
            parent_otel_span=parent_otel_span,
            proxy_logging_obj=proxy_logging_obj,
        )

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Re-authenticate through the UI/login flow to mint a fresh token under the current key — stale sessions are the most common cause
  2. Pin LITELLM_SALT_KEY (and any UI session encryption env) to a stable value across restarts/deployments so existing sessions remain valid
  3. If sessions must be invalidated wholesale after a key rotation, clear browser cookies/API clients of the old token

Example fix

# before (docker-compose, no pinned salt)
services:
  litellm:
    image: ghcr.io/berriai/litellm
# after
services:
  litellm:
    image: ghcr.io/berriai/litellm
    environment:
      LITELLM_SALT_KEY: "${LITELLM_SALT_KEY}"   # stable across restarts
Defensive patterns

Strategy: try-catch

Validate before calling

# before sending a stored UI token, sanity-check it still decrypts under this proxy
from litellm.proxy.auth.auth_checks import _get_ui_token_if_valid  # conceptual
assert token.startswith("sk-") or len(token) > 40, "stale/corrupt session token"

Try / catch

try:
    user_auth = decode_hashed_token(hashed_token)
except Exception as e:
    if "Invalid hash key" in str(e):
        # session minted under a different LITELLM_SALT_KEY: force re-login
        return RedirectResponse("/experimental/login/login")
    raise

Prevention

When it happens

Trigger: Presenting a UI session token minted under a different LITELLM_SALT_KEY/ui_hash_key than the running proxy uses (e.g. salt key rotated, container recreated without the env var), or a truncated/manually edited token in the Authorization header.

Common situations: Rotating or losing LITELLM_SALT_KEY so old UI sessions/cookies can no longer be validated; ephemeral Docker containers without a pinned salt key, invalidating sessions on each recreate; tokens persisted from a previous proxy version with a changed UserAPIKeyAuth schema.

Related errors


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