Use knotel / Debug and alert
Alerts
Get told when error rate, latency, traffic or matching log lines cross a line, by webhook or Slack.
An alert is a threshold on a number the dashboard already shows - error rate, p95, requests, or how many log lines matched - checked on a schedule and posted to a webhook (Slack or your own) when it crosses. There's no second query language: the filters are the same ones the Traces page puts in its URL.
Create an alert
Open a project's Alerts page and choose New alert. The editor is a page of its own that walks through what to watch, when it counts as broken, how patient to be and which webhook to tell, with a summary of the finished rule beside it. Next to the threshold it shows what the rule measures right now, from the same query the check runs, so the line is drawn against the real number. Each alert watches one of two things:
| Watch | Metrics | Narrowed by |
|---|---|---|
| Spans | Error rate, errors, requests, p50, p95, p99, average latency, slowest request | Any span filter (service, route, attribute). Entry spans counts requests into a service rather than every span under them. |
| Logs | Matching lines | A substring of the body (case-folded unless you ask otherwise), a minimum level, one service, and log attribute filters. |
Choose All projects and one rule covers every project, with its own state in each: one broken project is one incident to announce and to recover, not a number averaged with nine healthy ones until it stops crossing the line.
When it fires
| Setting | Default | What it does |
|---|---|---|
| Look back | 15 min (logs: 5) | The window each check measures. 5 minutes to a day. |
| Stays broken for | 0 min | How long the breach must last before anyone hears of it. The alert sits in pending until then, so one bad minute doesn't wake anyone. |
| Check every | 5 min | How often it's evaluated. The Worker's cron runs each minute and checks what's due. |
| Remind every | 60 min | While it stays broken, it re-sends on this cadence rather than every check. Without it a tripped alert sends 288 messages a day. |
- No data is not a breach. A window with nothing in it is skipped, not read as zero, so a "requests below 10" alert doesn't fire on an idle weekend. To catch a service going silent, watch a busier scope, or rely on Home's Went quiet signal.
- A resolve is sent once, when a firing alert comes back under the line. A breach that never got past pending was never announced, so it isn't resolved either.
- Muting stops sending, not checking. Mute for an hour across a deploy and an alert that broke and recovered inside it comes out quiet, rather than announcing a fire that's already over.
Webhooks
Alerts post to webhooks. A webhook is set up once, under Webhooks or right in the alert editor, and shared by every project. Each has a Send test that goes through the real delivery path, and the page remembers whether the last test worked. Webhooks are the only way alerts are sent for now; there is no email.
Slack
Create an incoming webhook in Slack and paste its URL as a webhook; knotel recognises it and picks the Slack format. Messages carry the condition, the measured value, the threshold and a button back to the project.
Your own receiver
The JSON format posts:
{
"alert": "Checkout error rate",
"state": "fired", // or "resolved"
"project": "shop",
"projectId": "p_…",
"condition": "error rate above 2% over 15 min",
"value": 4.7, // null in a test send
"threshold": 2,
"at": "2026-09-22T03:14:00.000Z",
"link": "https://trace.example.com/p/p_…"
}Generate a signing secret and each request carries x-knotel-signature: sha256=<hex>, the HMAC-SHA256 of the raw body. Check it before trusting the payload:
import { createHmac, timingSafeEqual } from "node:crypto";
function verified(rawBody: string, header: string | undefined, secret: string) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return !!header && header.length === expected.length &&
timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Extra headers, such as an Authorization your receiver wants, can be added to the destination. A request times out after 10 seconds; a 5xx or 429 is retried once, and any other error is not, since it won't fix itself.
PUBLIC_URL variable. Unset, they use the address knotel was last opened at, which is wrong behind a proxy that hides it. See Variables and secrets.What alerts cost
Checking is the cost, in ClickHouse CPU; sending is rounding error. Alerts due in the same minute that share a scope share one query, and an all-projects alert is one query grouped by project, not one per project. Each check reads only its look-back window, so a busy instance with many alerts still spends little time on them. Nothing is billed per query or per message.
Alerts worth starting with
| Alert | Settings |
|---|---|
| Checkout is failing | Spans · error rate above 2% · filter http.route = /checkout · look back 10 min · hold 5 min |
| API got slow | Spans · entry spans · p95 above 1500 ms · look back 15 min · hold 10 min |
| A worker is erroring | Logs · level ERROR · service billing-worker · matching lines above 20 · look back 5 min |
| A known failure is back | Logs · body contains "payment declined" · matching lines above 0 |
| Any project breaks | Spans · all projects · entry spans · error rate above 5% · hold 5 min |