unrent home

Alerts and webhooks

Unrent raises alerts from 16 rules and sends them by email to members who opt in and to up to 10 webhooks. Each webhook request is signed: verify x-unrent-signature as an HMAC-SHA256 of the raw body with your webhook secret.

Rules

Alert rules
RuleSeverityFires when
service_downcriticalA deployed service’s container has failed and no deploy is in progress
restart_loopcriticalThe container keeps restarting (3 or more in 10 minutes)
server_disconnectedcriticalNo connection from the agent for more than 2 minutes
server_silentwarningConnected, but no status for more than 5 minutes
disk_usagewarning / critical85 % / 95 % of the disk used
memory_pressurewarning / critical90 % / 97 % of memory used for 2 minutes
backup_latewarningNo verified backup within the interval plus a grace period (30 hours by default)
backup_failingcriticalThe last backup failed
drill_failedcriticalA restore drill failed
deploy_failedwarningA pull, deploy or rollback failed
config_failedwarningApplying config failed
cron_failedwarningA scheduled job exited non-zero
ingress_failedcriticalRoutes or certificates could not be applied
cert_expiringwarning / criticalA certificate expires in under 14 / 3 days
converge_failedwarningApplying hardening failed
hardening_driftwarningA hardened server fails a check in the nightly run

One alert per condition: it updates instead of repeating, escalates from warning to critical, reopens if it returns within 15 minutes, and is marked flapping after 4 changes in 30 minutes. Developers can acknowledge alerts and mute a rule for 5 minutes to 7 days. At most 30 notifications per organisation per hour are sent.

Email

Each member turns on Settings → Notifications → Email me for themselves. Messages come from no-reply@unrent.dev with the subject [Unrent] Critical: ….

Webhooks

Admins add webhooks: https on port 443, at a public address. The secret (whsec_…) is shown once. Each delivery is a POST with a JSON body:

Body
{
  "id": "dlv_…",
  "type": "alert.fired",
  "timestamp": 1763030000,
  "org_id": "org_…",
  "suppressed_last_hour": 0,
  "alert": { "id": "alr_…", "rule": "service_down", "severity": "critical", "state": "firing",
             "title": "Service production/web is down", "server_name": "web-1", "url": "https://app.unrent.dev/…" }
}

Types: alert.fired, alert.escalated, alert.resolved, alert.flapping, alert.acknowledged and webhook.test. Headers: x-unrent-event, x-unrent-delivery (the id) and x-unrent-signature: sha256=<hex>.

Verify a webhook

  1. Compute HMAC-SHA256 of the raw request body with your secret, as hex.
  2. Compare it with the value after sha256= in x-unrent-signature, in constant time.
  3. Reject a timestamp (seconds) older than 5 minutes.
  4. Deduplicate by id: a retry sends the identical body.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(rawBody, header, secret) {
  const expected = Buffer.from('sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex'));
  const given = Buffer.from(header ?? '');
  if (given.length !== expected.length || !timingSafeEqual(given, expected)) return false;
  const { timestamp } = JSON.parse(rawBody);
  return Math.abs(Date.now() / 1000 - timestamp) <= 300;
}

Answer with any 2xx. 408, 429, 5xx, timeouts (10 seconds) and connection errors are retried after 30 seconds, 2 minutes, 10 minutes, 1 hour and 6 hours; any other answer, redirects included, is final.

Updated . This page as Markdown.