Reference / Status Pages

Status Pages

Your public status page goes live in minutes. Define components + HTTP probe checks; the worker probes every 30 seconds and opens an auto-incident on sustained failure (optionally firing a restart target). Your end users subscribe by email, Telegram, or webhook — incident updates ship to them automatically.

Setup#

This lives on status.sentroy.com (not auth.sentroy.com). Get your first page live in 1 minute.

  1. Visit status.sentroy.com/d → log in with your Sentroy account → pick your company.
  2. Create the status page — the wizard asks for a name and a public slug (e.g. my-app). Public URL becomes https://status.sentroy.com/p/my-app.
  3. Add components — the user-facing service groups (API, Database, Web App). Each component is monitored by one or more HTTP checks.
  4. Define HTTP checks — per component: probe URL + interval (30s-3600s) + expected status range + optional degraded-latency threshold + optional auto-restart target binding.
  5. Settings → Branding — display name, primary color, logo URL, optional logo link, and tagline. Live preview reflects every change immediately.

Components & checks#

Each user-visible service on the public page is a component. Component status is derived from the worst severity across its checks.

Status derivation#

For each component, given its bound checks:

  • any check down → component is down (red)
  • any check degraded (responds 2xx but slow) → degraded (amber)
  • every check no-data → component is no-data (gray — never probed yet)
  • otherwise → operational (green)
  • if an active maintenance window covers this component, the status is overridden to maintenance (blue). Downtime during maintenance does NOT count against uptime.

HTTP check fields#

  • url + method (GET/POST/HEAD)
  • intervalSeconds — 30 minimum, 3600 maximum
  • expectedStatusMin/Max — defaults 200-299; define your own success range
  • expectedBodyContains — optional; if the response body lacks this string, marked down
  • timeoutMs + degradedLatencyMs — timeout exceeds triggers down; degradedLatency triggers degraded
  • insecureSkipTlsVerify — dev only

Incidents#

Communicate outages to your users — open one manually or let the worker do it for you.

Two types:

  • Manual — dashboard Incidents tab → "New incident". Provide title, impact (minor/major/critical), affected components, and an initial update message (TR + EN). Status: investigating → identified → monitoring → resolved.
  • Auto — the worker opens an incident when a check sees 3+ consecutive failures. It auto-resolves after 30 minutes of recovered operation. Auto incidents carry a source: "auto" badge; you can still post manual updates or resolve them yourself.

Timeline updates#

Every status transition + commentary is pushed as a new update. Update bodies are localized ({ tr, en }) — the public page renders the user's browser locale.

Maintenance windows#

Announce planned downtime in advance. Subscribers get notified, a banner appears on the public page, and uptime stats are unaffected.

Dashboard Maintenance tab → "Schedule maintenance". Set start/end, affected components, and a title + description (TR + EN). State machine:

  • scheduled — future start time. A reminder email is sent 1 hour before.
  • in_progress — UI "Start now" button or automatic when scheduledStart passes. Subscribers receive the "started" notification.
  • completed — UI "Mark complete" button. Subscribers receive the "completed" notification.
  • cancelled — cancelled before starting.

Restart targets#

Endpoints the worker calls automatically when a check fails N times in a row. Three target types: HTTP, SSH, and Coolify built-in.

HTTP target#

Your own /restart route, a Coolify webhook, or any HTTP endpoint. POST or GET, optional encrypted auth header, optional body, expected status range, timeout.

SSH target#

The worker SSHes into a remote host with your private key (PEM, stored AES-256-GCM encrypted) and runs a single command (e.g. docker restart api-server). Optional passphrase. Exit code 0 = success.

Coolify built-in target#

The worker calls Coolify's GET /api/v1/deploy?uuid=...&force=true endpoint with your API token (encrypted). Coolify queues the redeploy.

Bind a target to a check from Checks tab → check edit → "Auto-restart on failure" section → pick the target + threshold (N consecutive failures) + cooldown (minimum seconds between restarts; default 600 = 10 min).

The "test fire" button (⚡) in the dashboard manually triggers an HTTP target without going through threshold/cooldown — useful for verifying setup. SSH and Coolify targets only fire from scheduled execution (no manual test in v1).

Subscribers#

Your users subscribe on the public page by email, Telegram, or webhook. Each incident update + maintenance event triggers a notification automatically.

Three channels#

  • Email — double opt-in. The user enters an address, gets a confirmation link, clicks it, and is verified. Every notification carries a one-click "Unsubscribe" link.
  • Telegram — enter a chat ID and a bot token from @BotFather. The chat owner must send /start to the bot first. Sentroy sends a confirmation message to verify; the bot token is stored AES-256-GCM encrypted. No verification email step.
  • Webhook — server-to-server. Sentroy POSTs HMAC-SHA256 signed payloads to your URL. No verification step (URL owner has implicit consent).

Topic + component filters#

From the subscribe dialog, users can pick:

  • Topic — everything / incidents only / maintenance only
  • Components — only notify for events that affect these specific components (empty = all components)

Users can change these later from /p/[slug]/preferences?token=... (token is in every notification email footer).

Webhook signup example#

curl -X POST https://status.sentroy.com/api/v1/status/my-app/subscribe \
  -H "Content-Type: application/json" \
  -d '{
    "type": "webhook",
    "webhookUrl": "https://your-app.example.com/sentroy-status-webhook"
  }'
# Response: { "managementToken": "...", "webhookSecret": "swhs_..." }
# managementToken is the HMAC secret; shown once, store it now.

Sample webhook payload (incident update):

{
  "event": "incident.updated",
  "pageSlug": "my-app",
  "data": {
    "incidentId": "...",
    "incidentTitle": { "tr": "...", "en": "..." },
    "impact": "major",
    "affectedComponentIds": ["..."],
    "update": {
      "id": "...",
      "status": "monitoring",
      "body": { "tr": "...", "en": "..." },
      "createdAt": "2026-05-17T12:34:56Z"
    }
  },
  "timestamp": "2026-05-17T12:34:56.789Z"
}

Event types#

Sent as the X-Sentroy-Event header on webhook deliveries:

  • incident.updated — new timeline update (investigating / identified / monitoring)
  • incident.resolved — incident closed
  • maintenance.scheduled — new maintenance window created
  • maintenance.reminder — 1 hour before start
  • maintenance.started — window begins
  • maintenance.completed — window ends

Public snapshot API#

JSON snapshot of your status page — CORS-open, no auth required. Use it for embed widgets, monitoring dashboards, or your own UI.

GET /api/v1/status/[slug] — 30s ISR cache. Pass ?lang=tr or send an Accept-Language header to get incident and maintenance text in the requested locale (defaults to en).

curl -s https://status.sentroy.com/api/v1/status/my-app?lang=en | jq

Response shape:

{
  "page": {
    "name": "My App",
    "slug": "my-app",
    "branding": {
      "displayName": "My App Status",
      "primaryColor": "#3b82f6",
      "logoUrl": null,
      "logoLinkUrl": null,
      "tagline": null
    },
    "customDomain": null,
    "subscribersEnabled": true
  },
  "overall": "operational",
  "components": [
    {
      "id": "...",
      "name": "API",
      "status": "operational",
      "uptime24h": 99.95,
      "uptime30d": 99.99,
      "lastCheckedAt": "2026-05-18T12:34:56Z",
      "dailyHistory": [
        { "date": "2026-02-17", "status": "operational" },
        ...90 entries
      ],
      "checks": [
        { "id": "...", "name": "Health endpoint", "status": "operational", "lastLatencyMs": 142, "lastCheckedAt": "..." }
      ]
    }
  ],
  "activeIncidents": [
    {
      "id": "...",
      "title": "Mail delivery delays",
      "status": "monitoring",
      "impact": "major",
      "affectedComponentIds": ["..."],
      "startedAt": "...",
      "updates": [{ "id": "...", "status": "monitoring", "body": "Fix deployed; watching.", "createdAt": "..." }]
    }
  ],
  "upcomingMaintenances": [],
  "pastIncidents": [
    { "id": "...", "title": "...", "impact": "minor", "startedAt": "...", "resolvedAt": "...", "affectedComponentIds": [] }
  ],
  "generatedAt": "2026-05-18T12:34:58Z",
  "windowHours": 24
}

Embed widget#

Embed your status badge in your own site with an iframe. Locale-aware (?lang=), light/dark mode automatic.

The dashboard Overview → "Embed widget" panel shows the full snippet with your slug. Typical usage:

<iframe
  src="https://status.sentroy.com/p/my-app/embed"
  width="320"
  height="80"
  style="border:0"
  loading="lazy"
></iframe>

Webhook signature verification#

Sentroy signs every webhook payload with HMAC-SHA256. Your endpoint MUST verify the signature.

Sentroy signs each payload with the subscriber's managementToken using HMAC-SHA256. Check the X-Sentroy-Signature: sha256=<hex> header.

import { createHmac, timingSafeEqual } from "node:crypto"

function verifySentroyWebhook(rawBody, signatureHeader, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex")
  const provided = (signatureHeader || "").replace(/^sha256=/, "")
  if (provided.length !== expected.length) return false
  return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(provided, "hex"))
}

// Express example:
app.post("/sentroy-status-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verifySentroyWebhook(
    req.body, // RAW buffer — verify BEFORE JSON.parse
    req.headers["x-sentroy-signature"],
    process.env.SENTROY_WEBHOOK_SECRET, // managementToken from signup response
  )
  if (!ok) return res.status(401).end()

  const event = JSON.parse(req.body.toString())
  // ... your logic
  res.json({ ok: true })
})