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.
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 reportedWriting 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.
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")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.
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")))every alert type
test alert: 200
requests: 401 200
alerts: ['budget_alerts', 'llm_exceptions', 'llm_requests_hanging', 'llm_requests_hanging', 'llm_too_slow']
failure alert shows the prompt: True
llm_exceptions only, prompt hidden
test alert: 200
requests: 401 200
alerts: ['llm_exceptions']
failure alert shows the prompt: False
Alert type: `llm_exceptions`
Level: `High`
Message: LLM API call failed: `litellm.BadRequestError: GroqException - {"error":{"message":"Invalid API Key","type":"invalid_request_error","code":"invalid_api_key"}}
LiteLLM: model group 'broken' failed with the error above. No fallback was attempted.
Model: groq/openai/gpt-oss-120b
API Base: `https://api.groq.com/openai/v1`
model_group: `broken`
deployment: `groq/openai/gpt-oss-120b`
`Reading the alerts that arrived
- The first run received five alerts. The test alert from
/health/services?service=slackarrived as abudget_alertsmessage that says it is a test. The failed call gavellm_exceptions. With the tiny threshold,llm_requests_hangingfired for both requests andllm_too_slowfor 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_typeskept onlyllm_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_exceptionsthey 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
/slackto the Discord address and use it asSLACK_WEBHOOK_URL. - Microsoft Teams. The page says
ms_teamsinalertingsends alerts natively from v1.100.0, using the variableMS_TEAMS_WEBHOOK_URL. The installed 1.105 code acceptsms_teams. - A generic webhook for budget events. The page marks it beta.
alerting: ["webhook"]withWEBHOOK_URLposts a JSON object with fields such asspend,max_budgetandevent. - Several channels.
alert_to_webhook_urlmaps each alert type to its own address or list of addresses. - Digest mode.
alert_type_configgroups 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.
general_settings:
alerting: ["slack"]
alert_type_config:
llm_requests_hanging:
digest: true
digest_interval: 3600 # one summary per hourBudget 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
| Alerts | Prometheus metrics | Callbacks and logs | |
|---|---|---|---|
| Reach | A person, in chat | A dashboard or a rule | A tool such as Langfuse |
| Triggered by | A fault the gateway recognises | A query someone writes | Every call |
| Best for | Waking someone when something breaks | Trends and capacity | Looking at one request in detail |
Where you use alerting
- A model failing at night.
llm_exceptionsand the outage alerts reach the on-call person without anyone watching a dashboard. - Slow providers.
llm_too_slowflags responses past the threshold you set. - Budgets. Alerts for keys and teams near their limit, once a database is in place.
alert_types, and use digest mode for the types that repeat.Related
- Previous: Prometheus metrics
- Next: Model access groups
- See also: Virtual keys: a key per application for the budgets that budget alerts watch
- Reference: Alerting / Webhooks in the LiteLLM docs
- Change
alerting_thresholdto 300 in the template and run again. The two threshold alerts disappear and only the failure remains. - Set
alert_typesto["llm_too_slow"]and read which alert arrives. - Print the whole
llm_requests_hangingalert text and read which fields it carries.
Slow is fine. Stopping is the only problem.