Alert webhooks

A second channel for the alert that a job has failed, next to email and never instead of it.

Contract version 1.15.0 The reference below is read from the OpenAPI contract, which is written in English. It is not translated on purpose: translating it would mean copying it, and a copy drifts. Download the public contract (OpenAPI, JSON)

What a webhook is here

You register an https address; when a job has definitively failed, we send it a POST. Slack and Discord read one field and are given a line of text; anything else is given the JSON described below.

The channel belongs to you and not to the workspace. Two people on the same team have two different destinations, and no role — not even Admin — can read another member’s. A failing job should wake whoever is on call.

It is a channel in addition, never a channel instead of: email stays on by default, because a webhook that stops working would otherwise turn the alerts off without saying so.

Registering and inspecting a channel

A channel is registered, listed and removed from the dashboard, and those routes are not on this page because no key reaches them. They take a session and not an API key, and the asymmetry is deliberate: a key able to replace a webhook address could redirect a workspace’s alerts to a destination of its own — learning which jobs fail, and when — while the owner keeps seeing a channel that looks configured.

No route ever gives an address back, not even its last characters. What identifies a channel in a list is the name you chose and the host.

The events that leave

The events that may leave towards an external webhook. There is one, and it is what R29 asks for: "failure notifications".

Plan changes, welcomes and account security events stay out on purpose. A Slack channel is shared and sits outside our perimeter: those events concern a person and their relationship with us, and announcing them in a shared channel would hand them to colleagues who have nothing to do with it. Security is the least obvious of the four — an unauthorised sign-in is *the* thing one would want to know quickly — and it stays out because to be useful it would have to say *what* happened to the account, and that, in a channel, is an invitation.

NameTypeNotes
job_failed

What the body carries

The list is complete: it is read from the structure that produces the JSON, so it cannot be shorter than what is sent. Fields marked optional are absent when they have nothing to say.

What is not there is not missing. No error text, no response body, no target URL — since those can quote an address that carries resolved workspace secrets — no email address and no identifier of a person. A shared channel sits outside our perimeter, and a POST cannot carry what the structure does not have.

The body shape the target expects. It decides only the shape, never the destination: webhook sends the payload as JSON to any compatible endpoint.

NameTypeNotes
eventstringalways present
job_idstringoptional
job_namestringoptional
environmentstringoptional
failuresintegeroptional
last_attempt_atstring (date-time)optional
failure_kindstringoptional
http_statusintegeroptional

Signature and verification

There is no signature, and no header to verify. The address is the credential: the Slack and Discord tokens live in its path, which is why only https is accepted, why the address is encrypted at rest, and why no route ever reads one back to you.

What you can rely on instead is narrow and worth knowing. The call arrives over TLS and identifies itself as Postqron-Alerts/1. No redirect is ever followed: a 3xx is a final answer, and a failed one, so nothing can divert the body somewhere else after the fact.

If you need certainty that a call is ours, put a secret of your own in the path of the address you register and check it on arrival. That is the same property the signature would have given you, and it works today.

A delivery that does not go out is not a silence: it is a row with its reason and the status of the last response, readable in the dashboard next to the channel. After enough consecutive failures the channel is suspended, with the reason attached.