LiteLLMLiteLLM 1.105 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
48 small wins to finish your pathNext lesson →

Alerting

Alerting is the gateway posting a message to a chat webhook, such as a Slack channel, when a request fails, runs slow, hangs or crosses a budget.

Last updated: 12 Oct, 2026 · LiteLLM 1.105

A webhook is a web address that accepts a message as an HTTP POST and shows it in a chat channel. Prometheus metrics keep the numbers for someone to look at. Alerts interrupt a person when a number goes wrong. The alerting page lists what the gateway can watch: hanging, slow and failed model calls, model outages, budgets and spend, failed database reads and writes, and daily reports.

Syntax: alerting is switched on in general_settings. The webhook address is not in the config. The page says to put it in the environment variable SLACK_WEBHOOK_URL.

yaml
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."

# config.yaml
general_settings:
  alerting: ["slack"]
  alerting_threshold: 300   # seconds before a slow or hanging call is reported

Writing the helper that starts a gateway

ops.py starts the litellm command, waits until it answers, and stops it in a finally block. call() sends a request and returns the status, body and headers. chat() sends one chat request.

ExampleWriting ops.py
from pathlib import Path
import sys

Path("ops.py").write_text('''import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request
from contextlib import contextmanager

KEY = "sk-gateway-1234"          # the master key written in every config.yaml


@contextmanager
def running(port, env=None):
    """Start the gateway on `port` from config.yaml, wait until it answers, stop it afterwards."""
    litellm = os.path.join(os.path.dirname(sys.executable), "litellm")
    cmd = [litellm, "--config", "config.yaml", "--port", str(port)]
    proc = subprocess.Popen(cmd, env=env, stdout=open("gateway.log", "w"), stderr=subprocess.STDOUT)
    try:
        for _ in range(90):
            if proc.poll() is not None:
                raise RuntimeError("the gateway stopped; read gateway.log")
            try:
                urllib.request.urlopen(f"http://127.0.0.1:{port}/health/liveliness")
                break
            except OSError:
                time.sleep(1)
        yield f"http://127.0.0.1:{port}"
    finally:
        proc.terminate()
        proc.wait(10)


def call(url, path, body=None, key=KEY, headers=None):
    """Send one request. Returns (status, text, headers) and does not raise on 4xx or 5xx."""
    head = dict(headers or {})
    if key:
        head["Authorization"] = f"Bearer {key}"
    if body is not None:
        head["Content-Type"] = "application/json"
    data = json.dumps(body).encode() if body is not None else None
    try:
        reply = urllib.request.urlopen(urllib.request.Request(url + path, data=data, headers=head), timeout=60)
    except urllib.error.HTTPError as error:
        reply = error
    return reply.getcode(), reply.read().decode(), {k.lower(): v for k, v in reply.headers.items()}


def chat(url, model, text="Reply with one word: ok", key=KEY, headers=None, **extra):
    """One chat request. Extra keyword arguments are added to the JSON body."""
    body = {"model": model, "messages": [{"role": "user", "content": text}], "max_tokens": 60, **extra}
    return call(url, "/chat/completions", body, key, headers)
''')
sys.path.insert(0, ".")        # lets the programs below run: from ops import running, call, chat
print("wrote ops.py")

Catching alerts with a stand-in for Slack

This machine has no Slack workspace, so the example starts a small web server of its own and gives the gateway its address as SLACK_WEBHOOK_URL. The gateway posts the same JSON body it would send to Slack, with the message in a field named text, and the small server keeps every body it receives.

The receiving server

Hook answers every POST with 200 and stores the text field in a list. It runs in a background thread, and a finally block stops it.

Two gateways, one failure

The program runs the gateway twice with a threshold of 0.0001 seconds, which makes every call count as slow. The page uses an artificially low threshold the same way for testing. Both runs send the same two requests: one to a model with a rejected key and one to a working model. The first run leaves every alert type on. The second sets alert_types to llm_exceptions and sets redact_messages_in_exceptions, which the page gives as the way to keep the prompt out of alerts.

ExampleAPI keyAlerts caught by a local webhook, with and without filtering
import json
import os
import re
import threading
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from ops import running, call, chat

received = []

class Hook(BaseHTTPRequestHandler):                      # stands in for Slack
    def do_POST(self):
        size = int(self.headers["Content-Length"])
        received.append(json.loads(self.rfile.read(size))["text"])
        self.send_response(200)
        self.end_headers()
    def log_message(self, *args):                        # keep the output clean
        pass

server = HTTPServer(("127.0.0.1", 4729), Hook)
threading.Thread(target=server.serve_forever, daemon=True).start()

TEMPLATE = """model_list:
  - model_name: fast
    litellm_params:
      model: groq/openai/gpt-oss-20b
      api_key: os.environ/GROQ_API_KEY
  - model_name: broken
    litellm_params:
      model: groq/openai/gpt-oss-120b
      api_key: gsk_not_a_real_key
general_settings:
  master_key: sk-gateway-1234
  alerting: ["slack"]
  alerting_threshold: 0.0001
{extra}"""
RUNS = [
    ("every alert type", ""),
    ("llm_exceptions only, prompt hidden",
     "  alert_types: [\"llm_exceptions\"]\nlitellm_settings:\n  redact_messages_in_exceptions: true\n"),
]
env = {**os.environ, "SLACK_WEBHOOK_URL": "http://127.0.0.1:4729/hook"}

def kinds():
    return sorted(re.search(r"Alert type: `(\w+)`", m).group(1) for m in received)

try:
    for label, extra in RUNS:
        received.clear()
        Path("config.yaml").write_text(TEMPLATE.format(extra=extra))
        with running(4721, env=env) as url:
            print(label)
            print("  test alert:", call(url, "/health/services?service=slack")[0])
            print("  requests:", chat(url, "broken")[0], chat(url, "fast")[0])
            for _ in range(40):                          # alerts leave the gateway in batches
                if "llm_exceptions" in kinds():
                    break
                time.sleep(1)
            time.sleep(12)                               # room for the other alerts to arrive
        print("  alerts:", kinds())
        failure = next(m for m in received if "llm_exceptions" in m)
        print("  failure alert shows the prompt:", "Messages:" in failure)
finally:
    server.shutdown()
print()
print("\n".join(l for l in failure.splitlines() if not l.startswith("Timestamp")))

Reading the alerts that arrived

  • The first run received five alerts. The test alert from /health/services?service=slack arrived as a budget_alerts message that says it is a test. The failed call gave llm_exceptions. With the tiny threshold, llm_requests_hanging fired for both requests and llm_too_slow for the one that completed. In a real setup the threshold is minutes, so those two fire only for calls that are slow.
  • The second run received one alert. alert_types kept only llm_exceptions. The test alert was not delivered either, because it is a different type.
  • The prompt is in the first alert and missing from the second. By default an exception alert carries the messages sent to the model. With redact_messages_in_exceptions they are left out, which matters when prompts hold customer data.
  • The text printed at the end is the failure alert of the second run. It names the error, the model group, the deployment and the provider address. It says no fallback was attempted, because the group has none.

Sending alerts to Discord, Teams or a plain webhook

The page lists these destinations. None is run here, because each needs an account or a service.

  • Discord. The page says Discord offers a Slack-compatible webhook: append /slack to the Discord address and use it as SLACK_WEBHOOK_URL.
  • Microsoft Teams. The page says ms_teams in alerting sends alerts natively from v1.100.0, using the variable MS_TEAMS_WEBHOOK_URL. The installed 1.105 code accepts ms_teams.
  • A generic webhook for budget events. The page marks it beta. alerting: ["webhook"] with WEBHOOK_URL posts a JSON object with fields such as spend, max_budget and event.
  • Several channels. alert_to_webhook_url maps each alert type to its own address or list of addresses.
  • Digest mode. alert_type_config groups repeats of one alert type into a single summary message per time window, so a noisy type does not flood the channel. The page notes the digest lives in memory on one gateway copy.
yaml
general_settings:
  alerting: ["slack"]
  alert_type_config:
    llm_requests_hanging:
      digest: true
      digest_interval: 3600   # one summary per hour

Budget alerts come from keys with budgets, and the spend and daily reports summarise spend per team or tag. Keys and teams need a database, as Virtual keys: a key per application showed, so none of these is run here. Region outage alerts are marked an Enterprise feature on the page.

Alerts vs metrics vs logs

AlertsPrometheus metricsCallbacks and logs
ReachA person, in chatA dashboard or a ruleA tool such as Langfuse
Triggered byA fault the gateway recognisesA query someone writesEvery call
Best forWaking someone when something breaksTrends and capacityLooking at one request in detail

Where you use alerting

  • A model failing at night. llm_exceptions and the outage alerts reach the on-call person without anyone watching a dashboard.
  • Slow providers. llm_too_slow flags responses past the threshold you set.
  • Budgets. Alerts for keys and teams near their limit, once a database is in place.
Watch out. A very low threshold alerts on every call, as the tiny threshold in the run above did, and a channel that gets an alert for every request is soon muted. Start from the page's 300 seconds, filter with alert_types, and use digest mode for the types that repeat.
Try it yourself
  • Change alerting_threshold to 300 in the template and run again. The two threshold alerts disappear and only the failure remains.
  • Set alert_types to ["llm_too_slow"] and read which alert arrives.
  • Print the whole llm_requests_hanging alert text and read which fields it carries.

Slow is fine. Stopping is the only problem.