Settings: notifications¶
Outbound operational alerts to ntfy, Discord or a generic JSON webhook.
| Field | Type | Notes |
|---|---|---|
notifications |
NotificationsSettings | Outbound alert targets: ntfy/Discord/generic (below). |
Outbound operational alerts to ntfy, Discord, or a generic JSON webhook, a
renewal failure, an approaching or actual cert expiry, an upstream health
flap, an ACME account error, a frozen discovery reconciler, or (opt-in) a
config change. Delivery is asynchronous and best-effort, mirroring webhooks
above but sent through the separate internal/notify package (own targets,
own payload shapes, own event filtering). Empty (default) sends nothing.
| Field | Type | Required | Description |
|---|---|---|---|
targets |
[]NotificationTarget | no | The configured receivers (below). |
expiringThresholdDays |
int | no | Days before ACME expiry a certificate joins the daily cert.expiring digest. Default 14. |
NotificationTarget
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Name-safe identifier, shown in logs and status. |
type |
string | yes | ntfy, discord, or generic. |
url |
string | yes | Absolute http/https endpoint: an ntfy topic URL, a Discord webhook URL (must contain /api/webhooks/), or any endpoint for generic. |
secret |
Secret | no | ntfy access token or generic bearer token, sent as Authorization: Bearer <value>. Unused for discord: the webhook URL is itself the credential. |
disabled |
bool | no | Keep the target configured without firing it. |
events |
[]string | no | Subset of event kinds this target receives. Empty selects the default set (every kind below except config.changed). |
Event kinds
| Kind | When | Default on |
|---|---|---|
cert.renewal_failed |
An ACME issue/renew attempt fails, for any reason (missing DNS provider, solver build, EAB, account/directory client, the order itself). | yes |
cert.expiring |
Daily digest of every ACME certificate within expiringThresholdDays of notAfter. One message lists every cert, not one message per cert. |
yes |
cert.expired |
Daily digest of every ACME certificate already past notAfter. |
yes |
upstream.unhealthy |
An upstream group member's active/passive health check flips it down. | yes |
upstream.recovered |
The same member flips back up. | yes |
acme.account_error |
Reserved for ACME account/directory-level failures (currently folded into cert.renewal_failed; the kind exists for forward compatibility and manual test sends). |
yes |
discovery.frozen |
A Kubernetes Ingress or Docker discovery reconcile fails past the freeze boundary (managed hosts are left untouched, stale). | yes |
config.changed |
Any successful config write (the same events webhooks fires on). Noisy by default: opt-in per target. |
no |
Payload shapes
ntfy: POST to the topic URL with the message as the plain-text body;Title,Priority(3/4/5for info/warning/critical), andTagsheaders carry the summary and severity.secretrides asAuthorization: Bearer <token>.discord: POST{"content", "embeds": [{"title", "description", "color", "fields": [{"name", "value"}]}]}to the webhook URL. NoAuthorizationheader: the URL is the credential.generic: POST{"kind", "title", "body", "severity", "fields", "time"}as JSON, with the kind repeated in theX-GPM-Eventheader.secretrides asAuthorization: Bearer <token>.
Delivery is bounded, and drops rather than queues. At most 8 deliveries are
in flight at once. An event that would exceed that bound is dropped for that
target with a WARN, not queued: delivery is best-effort and must never block
or slow a config write. A target that is consistently dropping events is a
receiver that cannot keep up, not a gpm backlog to drain.
Checking a target, mirroring the webhook endpoints:
GET /api/notifications/status: one row per configured target with the outcome of its most recent send. In-memory and per-process; resets on restart.POST /api/notifications/{name}/test: sends a synthetic event and waits up to 5s for the answer, bypassing the target'seventsfilter and the dedup window. A disabled target is still tested. A refused or timed-out delivery comes back as200withok: false; only an unknown target name is a404.
notifications:
expiringThresholdDays: 14
targets:
- name: ntfy-ops
type: ntfy
url: https://ntfy.example.com/gpm-alerts
secret: ${FILE:/run/secrets/gpm_ntfy_token}
- name: discord-ops
type: discord
url: https://discord.com/api/webhooks/123456789012345678/example-token
events: [cert.renewal_failed, cert.expired, discovery.frozen]
- name: audit-log
type: generic
url: https://hooks.example.com/gpm-notify
secret: ${ENV:GPM_NOTIFY_TOKEN}
events: [config.changed]