Monitoring alerts via webhook
You run a monitoring tool — Uptime Kuma, healthchecks.io, a custom cron-with-webhook, anything that fires a webhook when something goes wrong. You want those alerts as email, ideally in the inbox you actually check. Posthorn handles this whether the monitoring tool POSTs application/x-www-form-urlencoded (most can) or JSON via API mode (Grafana, Alertmanager, etc.).
At the end: an /api/alert endpoint that takes form-encoded POSTs from your monitoring tool and emails you. Honeypot off (no humans). Origin check off (monitoring tools don’t send Origin headers). Rate limit calibrated to your alert volume.
Prerequisites
Section titled “Prerequisites”- A monitoring tool that can be configured to send form-encoded webhooks. Confirmed compatible (with their respective configuration tricks): Uptime Kuma (
Genericnotification type,Content-Type: application/x-www-form-urlencoded), healthchecks.io (Add Integration → Webhook, set body to form-encoded), generic cron-with-curl. - Same Posthorn baseline as the other recipes — Postmark account, server token, Docker host.
Walkthrough
Section titled “Walkthrough”-
Add an alert endpoint to
posthorn.toml.[[endpoints]]path = "/api/alert"to = ["alerts@yourdomain.com"]from = "Posthorn Alerts <noreply@yourdomain.com>"required = ["service", "status"]subject = "[{{.status}}] {{.service}}"body = """Service: {{.service}}Status: {{.status}}Details: {{.details}}"""[endpoints.transport]type = "postmark"[endpoints.transport.settings]api_key = "${env.POSTMARK_API_KEY}"[endpoints.rate_limit]count = 30interval = "1m"Note what’s not in this config:
- No
honeypot— there are no humans on this endpoint - No
allowed_origins— monitoring tools don’t sendOriginorRefererheaders; settingallowed_originswould block every alert - No
redirect_*— monitoring tools don’t follow redirects in a useful way - Higher
rate_limit— alert storms during outages are real, but bounded; 30/minute lets a burst through without the rate limiter eating the signal
- No
-
Restart Posthorn.
Terminal window docker compose restart posthorn -
Configure your monitoring tool to send form-encoded POSTs.
In Uptime Kuma’s notification settings:
-
Type: Webhook
-
POST URL:
https://yourdomain.com/api/alert -
Content-Type:
application/x-www-form-urlencoded -
Custom body (templated, Kuma syntax):
service={{name}}&status={{status}}&details={{msg}}
Kuma URL-encodes the values automatically.
In healthchecks.io’s project integrations:
- Add Integration → Webhook
- Method: POST
- URL (down):
https://yourdomain.com/api/alert - Body (down):
service={{ name }}&status=DOWN&details=Check did not ping in time - Headers:
Content-Type: application/x-www-form-urlencoded
Configure a corresponding “up” webhook with
status=UPfor recovery alerts.The simplest possible alert source. Drop this in
crontab -e:Terminal window */5 * * * * curl -s --fail https://api.yourservice.com/healthz || \curl -s -X POST https://yourdomain.com/api/alert \--data-urlencode "service=api.yourservice.com" \--data-urlencode "status=DOWN" \--data-urlencode "details=healthz check failed at $(date -Iseconds)" -
-
Send a test alert.
Terminal window curl -i -X POST https://yourdomain.com/api/alert \--data-urlencode "service=test-service" \--data-urlencode "status=DOWN" \--data-urlencode "details=Test alert from manual curl"Expect HTTP 200 with a submission_id. Email arrives within seconds with the subject
[DOWN] test-service.
Calibrating rate limits for alerts
Section titled “Calibrating rate limits for alerts”Alert traffic has a different shape than form traffic. A few patterns to think about:
- Quiet stretches punctuated by bursts. Days of no alerts; then a network blip and your monitor fires 10 alerts in 2 minutes. The rate limit needs to let the burst through.
- Per-service vs. total. Posthorn rate-limits per client IP, not per
servicefield. If you have one monitoring tool firing all alerts, all alerts come from one IP — so the limit applies to total alert volume, not per-service. - Trade-off: too tight, you lose alerts during the outages you care about. Too loose, a misconfigured monitor in a tight loop fills your inbox.
A sane starting point: count = 30, interval = "1m". Bump higher if you have many services or a tool that emits separate alerts per check. Add trusted_proxies if your monitoring tool is behind a reverse proxy you control.
Common gotchas
Section titled “Common gotchas”| Symptom | Likely cause | Fix |
|---|---|---|
HTTP 400 form-encoded body required | The monitoring tool is sending JSON, not form-encoded | Switch the tool to form-encoded, or reconfigure the endpoint as API mode to accept JSON natively |
| Alerts arrive without details | details isn’t in the required list, so when it’s missing or empty, Posthorn renders Details: with nothing after it | Either add details to required (forces the monitor to include it) or accept the empty case |
| First alert during an outage shows up, the rest get 429’d | Rate limit is set too tight for your alert volume | Bump count up; storms during outages are normal |
| Alert email lands in spam | DomainKeys Identified Mail (DKIM) / Sender Policy Framework (SPF) on the sending domain — same as every other Posthorn recipe | See DNS |
Where to go next
Section titled “Where to go next”- Configuration reference — every field
- Rate limiting — math behind the token bucket
- API mode — natural fit for Grafana, Alertmanager, and other JSON-only monitors