Skip to content

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.

  • A monitoring tool that can be configured to send form-encoded webhooks. Confirmed compatible (with their respective configuration tricks): Uptime Kuma (Generic notification 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.
  1. 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 = 30
    interval = "1m"

    Note what’s not in this config:

    • No honeypot — there are no humans on this endpoint
    • No allowed_origins — monitoring tools don’t send Origin or Referer headers; setting allowed_origins would 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
  2. Restart Posthorn.

    Terminal window
    docker compose restart posthorn
  3. 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.

  4. 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.

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 service field. 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.

SymptomLikely causeFix
HTTP 400 form-encoded body requiredThe monitoring tool is sending JSON, not form-encodedSwitch the tool to form-encoded, or reconfigure the endpoint as API mode to accept JSON natively
Alerts arrive without detailsdetails isn’t in the required list, so when it’s missing or empty, Posthorn renders Details: with nothing after itEither 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’dRate limit is set too tight for your alert volumeBump count up; storms during outages are normal
Alert email lands in spamDomainKeys Identified Mail (DKIM) / Sender Policy Framework (SPF) on the sending domain — same as every other Posthorn recipeSee DNS