Expand description
The webhook notify backend: one HTTP request per event, with the URL, the
method, the headers and the body all stated by the operator.
This is the backend that exists so a chat provider is configuration.
Slack, Mattermost, Microsoft Teams, Telegram and Matrix differ in a URL, a
verb, an Authorization header and a JSON shape, and nothing else — so
there is one backend and a recipe table in the book, rather than one
backend per provider each carrying its own copy of the transport, the
timeout and the retryable/permanent split.
§Two-stage rendering
webhook/<event>.j2— embedded, and overridable file by file throughnotify.template_dirlike every other template — renders the human-readablemessage.- The entry’s own
bodykey is a template compiled at startup, withmessage,hookand every field of the event’s payload in scope.
The split is what lets an operator restyle every message by overriding six files, or restructure one provider’s payload by editing one config line, without either choice implying the other.
| tojson in a body template is not decoration. A .j2 template has
auto-escaping off — deliberately, unlike the web admin’s .html — so a
challenge_failed whose error text holds a quote or a newline would
otherwise render a payload the receiving provider answers 400 to, once,
permanently, for exactly the events an operator most wanted to hear about.
Written on hyper + tokio-rustls like every other outbound hop here (see
crate::http_client), rather than pulling in reqwest for one POST.