Supersedes #1045, #1507, and arguably #383/#1540)
A webhook channel is configured by three things — type, format, url — where format picks from a closed set (auto, slack-blockkit, slack-legacy, raw-data). You can point it anywhere, but you can't influence what Logfire actually sends.
That one limitation shows up as several open issues that look separate but aren't:
| Issue |
Wanted |
Blocked by |
| #1045 |
MS Teams / Google Workspace |
body shape |
| #1507 |
DingTalk |
body shape |
| #1540 |
Google Chat |
body shape |
| (this one) |
any authenticated endpoint |
request headers |
All of them get answered the same way today: run a small proxy that reshapes the payload and adds an Authorization header. As #1507 puts it, that's "a potential failing point" sitting in the alerting path.
My case
I'd like alerts to reach an automated agent (a Claude Routine) that starts root-cause analysis when something fires, opens a GitHub issue where warranted but I can't add auth. Slack and Discord work today only because their webhook URL is the credential.
Proposal
Add two optional fields to the webhook config, used when format = "custom":
Purely additive.
PagerDuty already needed its own channel type partly because a webhook couldn't produce its body shape or set its Authorization header. Templating turns that class of integration into configuration rather than new provider code.
Example alert (following was "SELECT 1 AS canary" to illustrate, from a real capture:
{
"organization_name": "acme",
"project_name": "acme-qa",
"alert_name": "Example alert",
"timestamp": "2026-08-02T21:00:45.204370Z",
"n_rows": 1,
"data": [[1]],
"columns": [
{ "name": "canary", "type": { "type_name": "Int64", "is_array": false }, "nullable": false }
],
"errors": null,
"description": "Example description",
"links": {
"org": "https://logfire-eu.pydantic.dev/acme",
"project": "https://logfire-eu.pydantic.dev/acme/acme-qa",
"alert": "https://logfire-eu.pydantic.dev/acme/acme-qa/alerts/<id>?alertRunId=<run-id>",
"alert_settings": "https://logfire-eu.pydantic.dev/acme/acme-qa/alerts/<id>?alertRunId=<run-id>/edit",
"explore": "https://logfire-eu.pydantic.dev/acme/acme-qa/explore?tab=...&query=...&since=...&until=..."
}
}
Most of that is already template-friendly; links in particular is exactly what a Teams or Google Chat card wants.
Data is positional.. arrays of values, with names carried separately in columns... so possibly needs a "rows" view (data) and json helper (positioning).
Header values are the same class of secret as the webhook URL itself, which is already masked on read so they could just get the same treatment, delivery logs included.
On send path; similar to current approach, need to succeed before saving (although potentially via API we don't need to do this, to start with?)
Supersedes #1045, #1507, and arguably #383/#1540)
A webhook channel is configured by three things —
type,format,url— whereformatpicks from a closed set (auto,slack-blockkit,slack-legacy,raw-data). You can point it anywhere, but you can't influence what Logfire actually sends.That one limitation shows up as several open issues that look separate but aren't:
All of them get answered the same way today: run a small proxy that reshapes the payload and adds an
Authorizationheader. As #1507 puts it, that's "a potential failing point" sitting in the alerting path.My case
I'd like alerts to reach an automated agent (a Claude Routine) that starts root-cause analysis when something fires, opens a GitHub issue where warranted but I can't add auth. Slack and Discord work today only because their webhook URL is the credential.
Proposal
Add two optional fields to the webhook config, used when
format = "custom":headers— a map of extra headers sent with each request. Covers auth. Same shape Grafana, Datadog and Alertmanager use.body_template— a Handlebars template rendering the body. Covers Customizable Webhook PayLoad Templates #1045, alter channel supports body transform #1507 and Add Google Chat webhook support #1540 together.Purely additive.
PagerDuty already needed its own channel type partly because a webhook couldn't produce its body shape or set its
Authorizationheader. Templating turns that class of integration into configuration rather than new provider code.Example alert (following was "SELECT 1 AS canary" to illustrate, from a real capture:
{ "organization_name": "acme", "project_name": "acme-qa", "alert_name": "Example alert", "timestamp": "2026-08-02T21:00:45.204370Z", "n_rows": 1, "data": [[1]], "columns": [ { "name": "canary", "type": { "type_name": "Int64", "is_array": false }, "nullable": false } ], "errors": null, "description": "Example description", "links": { "org": "https://logfire-eu.pydantic.dev/acme", "project": "https://logfire-eu.pydantic.dev/acme/acme-qa", "alert": "https://logfire-eu.pydantic.dev/acme/acme-qa/alerts/<id>?alertRunId=<run-id>", "alert_settings": "https://logfire-eu.pydantic.dev/acme/acme-qa/alerts/<id>?alertRunId=<run-id>/edit", "explore": "https://logfire-eu.pydantic.dev/acme/acme-qa/explore?tab=...&query=...&since=...&until=..." } }Most of that is already template-friendly; links in particular is exactly what a Teams or Google Chat card wants.
Data is positional.. arrays of values, with names carried separately in columns... so possibly needs a "rows" view (data) and json helper (positioning).
Header values are the same class of secret as the webhook URL itself, which is already masked on read so they could just get the same treatment, delivery logs included.
On send path; similar to current approach, need to succeed before saving (although potentially via API we don't need to do this, to start with?)