Email notifications in Proxmox VE work, but they have a habit of landing where you're not looking. A backup failure email sits in a spam folder, or in an inbox you only open twice a day, while the storage volume it warned you about keeps filling up. If you want to know about a problem the moment it happens — on your phone, in a chat channel you're already watching — email is the wrong tool for the job.
Since Proxmox VE 8.1, the notification system supports two target types built for exactly this: Gotify, a self-hosted push notification server with dedicated mobile apps, and Webhook, a generic HTTP target that can forward alerts to Discord, Slack, ntfy, Telegram (via a relay), Home Assistant, or any endpoint that accepts a JSON payload. Neither requires an SMTP relay, an app password, or anything mail-related at all.
This guide covers setting up both target types from scratch, wiring them into matchers so the right severity goes to the right place, and the templating syntax Proxmox uses to build the actual message body that gets sent out.
What You Will Learn
- How the Gotify and Webhook target types differ from the mail-based targets, and when to use each
- How to stand up a minimal Gotify server and connect it to Proxmox VE
- How to configure a Webhook target with a real example (a Discord channel), including the Handlebars templating Proxmox uses to build the request body
- How to create matchers that route specific severities or event types to specific targets
- How to use
match-calendarto route noisy, non-urgent alerts differently outside business hours - Common errors with both target types and how to fix them
What Is This Feature?
Proxmox VE's notification system, introduced in 8.1 and carried into 9.x, is built from two pieces: targets (where a notification goes) and matchers (rules deciding which events go to which targets). Mail-based targets — Sendmail and SMTP — were the original options. Gotify and Webhook were added as first-class target types in the same release, using the same matcher system underneath.
A Gotify target talks to a Gotify server — a small, self-hosted push notification service with a web UI and native Android/iOS-adjacent clients (there's an official Android app; iOS relies on third-party clients or the web UI). Proxmox sends a message to your Gotify server's API using an application token, and the Gotify server pushes it out to any connected client in real time.
A Webhook target is more general: Proxmox performs an HTTP request (GET, POST, or PUT) against a URL you configure, with a body and headers you define using Handlebars-style templates. This is what lets a single feature integrate with Discord, Slack, ntfy.sh, a Home Assistant automation, or an internal alerting system — anything that can receive an HTTP request can receive a Proxmox notification.
Both target types are configured the same way as SMTP and Sendmail targets: under Datacenter → Notifications in the GUI, or via pvesh from the shell, with the public configuration stored in /etc/pve/notifications.cfg and secrets (tokens, webhook secrets) stored separately in /etc/pve/priv/notifications.cfg, which is readable only by root.
Why Would You Use It?
You'll actually see it in time. Push notifications through Gotify show up on your phone's lock screen instantly. Webhook notifications into a Discord or Slack channel show up wherever your team already looks for alerts. Email, by contrast, competes with everything else in your inbox and is easy to miss or auto-filter.
No mail server dependency. SMTP targets need a relay, credentials, and often an app password (Gmail, for instance, no longer accepts a plain account password for SMTP). Sendmail targets need working local mail delivery. Gotify only needs a small server you control; Webhook needs nothing but a URL.
One feature, many destinations. Because a webhook is just an HTTP request with a templated body, the same underlying feature integrates with Discord, Slack, ntfy, Telegram (through a small relay), Microsoft Teams, PagerDuty, or a homegrown alerting endpoint — without Proxmox needing to know anything about any of them specifically.
Better for teams. A Discord or Slack channel is a shared destination everyone on a team already has open. A personal inbox is not. If more than one person needs to see cluster alerts, a webhook into a shared channel gets there faster than distribution lists.
Prerequisites
- Proxmox VE 8.1 or later (this guide applies unchanged to 9.x)
- Administrator access to the Proxmox web interface, or root shell access for the
pveshexamples - For the Gotify section: a Gotify server reachable from your Proxmox host. This can be a container on Proxmox itself, a Docker host elsewhere, or any Linux box — the example below uses Docker
- For the Webhook section: a webhook URL to send to. This guide uses a Discord channel webhook as a concrete example, but the same steps apply to Slack, ntfy, or any HTTP endpoint
Step-by-Step Tutorial
Part 1: Setting Up a Gotify Target
Step 1: Stand up a Gotify server
If you don't already have one running, the fastest path is Docker, on any Linux host with a few hundred MB free (an existing LXC container or a small dedicated VM both work fine):
docker run -d --name gotify \
-p 8888:80 \
-v /var/lib/gotify:/app/data \
gotify/server
This exposes the Gotify web UI on port 8888. Browse to http://<server-ip>:8888 and log in with the default credentials (admin / admin) — change the password immediately under Clients → admin user settings, since this is reachable over your network.
Step 2: Create a Gotify application and grab its token
In the Gotify web UI, go to Apps, click Create Application, and name it something identifiable like proxmox-alerts. Gotify generates a token immediately — copy it now, since this is what Proxmox will authenticate with. Each application gets its own token, so if you ever want to revoke Proxmox's access specifically without affecting other integrations, you delete this one application.
Step 3: Add the Gotify target in Proxmox VE
In the Proxmox web interface, go to Datacenter → Notifications, click Add in the Targets section, and choose Gotify. Fill in:
- Target Name: a short identifier, e.g.
gotify-phone - Server: the base URL of your Gotify instance, e.g.
http://192.168.1.50:8888 - Token: the application token from Step 2
Click Create. Proxmox stores the token in /etc/pve/priv/notifications.cfg, not the public config file, so it isn't exposed to anyone who can read /etc/pve/notifications.cfg.
Step 4: Test it
Select the new target in the list and click Test. If your Gotify app (or the web UI, which shows push messages too) is open, you should see a test notification within a second or two. If nothing arrives, double-check the server URL is reachable from the Proxmox host specifically — test with curl http://192.168.1.50:8888/health from the Proxmox shell before assuming Proxmox itself is misconfigured.
Part 2: Setting Up a Webhook Target
Step 5: Get a webhook URL
For this example, in Discord: open the target channel's settings, go to Integrations → Webhooks, click New Webhook, name it, and copy the webhook URL. Slack and ntfy.sh have equivalent one-click webhook URL generation if you'd rather use those instead.
Step 6: Add the Webhook target in Proxmox VE
Back in Datacenter → Notifications, click Add and choose Webhook. The fields are more involved than Gotify's, since a webhook target has to describe an entire HTTP request:
- Target Name: e.g.
discord-ops-channel - URL: the Discord webhook URL from Step 5
- Method:
POST - Header: add one header,
Content-Type: application/json - Body: the JSON payload, using Proxmox's templating syntax
Discord expects a JSON body with a content field. A minimal body that surfaces the important fields looks like this:
{
"content": "**[{{ escape severity }}]** {{ escape title }}\n{{ escape message }}"
}
The {{ title }}, {{ message }}, and {{ severity }} placeholders are filled in by Proxmox from the actual event. The escape helper escapes characters that would otherwise break the JSON syntax (quotes, newlines) — always wrap dynamic values in escape when building a JSON body, or a message containing a quote character will produce invalid JSON and a silent delivery failure.
Other placeholders worth knowing: {{ timestamp }} (Unix epoch seconds) and {{ fields.<name> }}, which exposes event-specific metadata such as fields.hostname or fields.type depending on what triggered the notification. If a webhook target needs a value that shouldn't live in the plaintext config — an API key some downstream service expects in a header, for example — store it under that target's Secrets section instead of the body, and reference it as {{ secrets.<name> }}. Secrets are written to /etc/pve/priv/notifications.cfg alongside Gotify and SMTP credentials, never to the public config.
Step 7: Test the webhook target
Save the target, select it, and click Test. A message should appear in the Discord channel within a couple of seconds. If it doesn't, see the Common Errors section below — webhook targets fail more often on body formatting than Gotify targets do, since you're hand-writing the payload.
Part 3: Routing Events With Matchers
Step 8: Create a matcher for critical alerts
Targets do nothing without a matcher pointing events at them. Go to the Matchers section, click Add, and name it something like critical-to-phone. Set Match Severity to error (and optionally warning), and under Target select your Gotify target. This routes anything Proxmox considers urgent — failed backups, node fencing, replication failures — straight to your phone.
Step 9: Create a second matcher for team visibility
Add another matcher, all-events-to-discord, this time with no severity filter (or a broader one including info and notice), pointed at your Discord webhook target. This gives the whole team a running log of everything Proxmox reports, while the Gotify matcher handles the "wake someone up" tier separately.
Step 10 (optional): Use match-calendar for quiet hours
If you don't want routine, non-critical notices pushed to your phone at 3 AM, add a Match Calendar rule to a matcher, e.g. mon-fri 9-17, so it only fires during business hours. Combine this with a separate always-on matcher for genuinely critical severities, so quiet hours suppress noise without suppressing real problems.
Commands Explained
| Command / Location | What it does |
|---|---|
pvesh get /cluster/notifications/targets | Lists all configured targets of every type, including Gotify and Webhook, from the shell |
pvesh get /cluster/notifications/matchers | Lists all configured matchers and the rules attached to each |
pvesh create /cluster/notifications/endpoints/gotify --name gotify-phone --server http://192.168.1.50:8888 --token <token> | Creates a Gotify target from the command line instead of the GUI |
pvesh create /cluster/notifications/endpoints/webhook --name discord-ops-channel --url <url> --method POST | Creates a Webhook target from the command line (headers and body are set as additional parameters or edited afterward via the GUI) |
/etc/pve/notifications.cfg | Public configuration: target names, server URLs, matcher rules. Safe to inspect, but edit through the GUI or pvesh to avoid syntax errors |
/etc/pve/priv/notifications.cfg | Sensitive values only — Gotify tokens, webhook secrets, SMTP passwords. Root-readable only |
curl http://<gotify-ip>:8888/health | Quick reachability check for a Gotify server from the Proxmox shell, independent of Proxmox's own test button |
Common Errors
Gotify target test fails with "unauthorized" or 401
The application token is wrong, was regenerated in Gotify after you copied it, or the application it belongs to was deleted. Create a fresh application in Gotify and update the token on the Proxmox target.
Webhook test fails silently, nothing arrives, no error shown
Almost always malformed JSON in the body template — usually a dynamic value like {{ message }} used without the escape helper, so a quote character or newline in the actual notification text breaks the JSON structure. Wrap every dynamic placeholder in escape inside a JSON body.
Webhook returns 400 from Discord specifically
Discord's webhook API expects a top-level content field (or embeds for richer formatting) and will reject a body that doesn't match its schema, even if the JSON itself is syntactically valid. Double-check the body against Discord's own webhook documentation if you're customizing beyond the minimal example above.
Gotify connection times out from Proxmox but the server works fine in a browser
Usually a firewall rule on the Gotify host, or the Gotify container/VM sitting on a different Proxmox bridge or VLAN than expected. Confirm the Proxmox host itself — not just your laptop — can reach the Gotify server's IP and port.
Troubleshooting
- No matcher, no notifications. Same rule as every other target type: a target with no matcher pointing at it never fires, even if its own test succeeds. This is the most common reason "I set it up and nothing happens."
- Check the scheduler log for the real error. Run
journalctl -u pvescheduler -n 50right after a failed test. The GUI's error message is often generic; the actual HTTP status code or connection error from the target shows up in the log. - Validate webhook JSON separately. If a webhook body is complex, build and test the rendered JSON in a text editor or with
python3 -m json.toolbefore pasting it into the Body field, so you catch syntax mistakes before Proxmox does. - Confirm secrets are actually referenced, not hardcoded. If a webhook needs an API key in a header, make sure the header value reads
{{ secrets.api_key }}and that the secret was actually saved under that name — a typo here fails silently rather than throwing a clear error. - Test both target types independently before layering matchers on top. Confirm the Gotify and Webhook targets each work with the built-in Test button first; only then build the matchers that route real events to them, so you're not debugging two layers of configuration at once.
Best Practices
- Route by severity, not by "everything to everywhere." Critical, actionable alerts to a push target like Gotify; routine informational events to a shared channel via webhook, where they can be scanned rather than reacted to individually.
- Always wrap dynamic values in
escapewhen writing a JSON webhook body — it costs nothing when the value is clean and prevents an intermittent, hard-to-reproduce failure when it isn't. - Keep at least one broad, unfiltered matcher as a catch-all, in addition to any narrow severity- or calendar-based matchers, so nothing falls through the cracks of an overly specific rule.
- Store any credential a webhook needs — API keys, shared secrets — in the target's Secrets section, never inline in the body or URL, since the body and URL fields are visible in the public
/etc/pve/notifications.cfg. - If you run a cluster, remember notification configuration is cluster-wide, not per-node — you only need to configure targets and matchers once, not on every node.
- Revisit and re-test these targets after any Gotify upgrade or Discord/Slack webhook rotation; both can silently invalidate an existing token or URL.
Frequently Asked Questions
Can I use Gotify and Webhook targets alongside email notifications?
Yes. All target types coexist under the same notification system, and a single matcher can even point at more than one target, so a critical event can go to email, Gotify, and a webhook simultaneously if you want that redundancy.
Does the webhook target work with Slack and Telegram, not just Discord?
Slack works the same way as Discord — generate an incoming webhook URL and adjust the body to Slack's expected text field. Telegram doesn't accept arbitrary webhook payloads directly, so it typically needs a small relay service in between (or a service like ntfy.sh, which has native Telegram forwarding) rather than pointing Proxmox straight at the Telegram Bot API.
Does my Gotify server need to be reachable from the internet?
No. It only needs to be reachable from the Proxmox host itself. Most homelab and small business setups run Gotify entirely on the local network, with the Gotify Android app connecting over Tailscale, WireGuard, or a reverse proxy exposed only to trusted networks.
What happens if I configure a target but never create a matcher?
Nothing gets sent, ever, even though the target itself will pass its own Test button check. Matchers are what actually connect an event to a target; a target with no matcher is inert.
Can a webhook target send to more than one place at once?
Each webhook target sends to exactly one URL, but you can create multiple webhook targets (one per destination) and have a single matcher route to all of them, or create separate matchers per destination if you want different filtering per channel.
Conclusion
Gotify and Webhook targets close the gap that email notifications leave open: alerts that actually reach you in the moment, wherever you're already looking. Gotify gives you a dedicated, self-hosted push channel with almost no setup overhead; Webhook turns Proxmox into just another integration your existing Discord, Slack, or ntfy setup already understands. Between the two, there's rarely a reason to depend on email alone for anything you'd want to know about immediately.
If your Proxmox host is still relying only on the default mail-to-root behavior, wiring up even one of these — paired with a matcher that actually routes to it — is a fifteen-minute change that pays for itself the first time it catches something before you would have.