Skip to content

Application Health Monitoring ​

Monitors a custom health endpoint that reports your application's internal status. Unlike uptime checks that only verify HTTP availability, application health monitoring checks the status of internal components like your database, cache, queue, and external services.

How It Works ​

Hoppla calls your health endpoint at the configured interval and evaluates the JSON response. The endpoint follows the Oh Dear health-check format: it returns a finishedAt timestamp plus a checkResults array, where each entry describes one internal component (database, cache, queue, external service, …). Hoppla aggregates the individual check statuses into one overall result.

Expected Endpoint Format ​

Your health endpoint should return a JSON response with a finishedAt timestamp and a checkResults array. Each entry has name, status, and optional notificationMessage, shortSummary, and meta fields:

json
{
  "finishedAt": "2026-06-01T12:00:00+00:00",
  "checkResults": [
    {
      "name": "Database",
      "status": "ok",
      "notificationMessage": "Database connection healthy",
      "shortSummary": "OK",
      "meta": { "connection_time_ms": 4 }
    },
    {
      "name": "Queue",
      "status": "warning",
      "notificationMessage": "Queue backlog growing",
      "shortSummary": "1,204 jobs",
      "meta": { "pending": 1204 }
    }
  ]
}

Status Values ​

Each check in checkResults reports one of the following per-check status values:

statusMeaningEffect
okComponent is healthyPasses
warningComponent is degraded but functionalOverall warning
failedComponent check failedOverall incident
crashedComponent check threw / could not runOverall incident
skippedCheck was skippedTreated as passing

Hoppla computes the overall status from the individual checks: fail (incident) if any check is failed or crashed, warning if any check is warning (or the finishedAt timestamp is stale), otherwise ok.

Configuration ​

SettingDescriptionDefault
Health endpoint pathRelative path appended to the monitor URL (absolute URLs are rejected for SSRF safety)health
Health check secretSent to your endpoint as the oh-dear-health-check-secret request header(none)
TimeoutMaximum time to wait for the response (seconds, 1–60)10
Stale thresholdWarn if finishedAt is older than this many minutes5
Check intervalHow often to poll the health endpoint5 minutes

The health endpoint is always a relative path appended to the monitor URL — for example, path health on monitor https://example.com resolves to https://example.com/health. Absolute URLs are not accepted.

Results ​

FieldDescription
finished_atThe finishedAt timestamp reported by your endpoint
checksThe parsed individual checks (name, status, message, short summary, meta)
checks_totalNumber of checks reported
checks_failedNumber of checks that failed or crashed
checks_warningNumber of checks in a warning state

Example Result ​

Health check: https://example.com/health (200 OK)

  finished_at:    2026-06-01T12:00:00+00:00
  checks_total:   2
  checks_failed:  0
  checks_warning: 1
  Checks:
    Database:  ok
    Queue:     warning  (1,204 jobs)  ← flagged

Alerts ​

EventTriggerSeverity
incident.createdAny check reports failed/crashed, the endpoint returns a 5xx, or the request fails entirelyIncident
warning.application_healthAny check reports warning, the finishedAt timestamp is stale, the endpoint returns 401/403 (auth misconfigured) or 404/501 (not configured), or the body is not valid JSONWarning

A 5xx response (or a transport-level failure) opens an incident. A 401 or 403 indicates the secret is misconfigured, and a 404 or 501 indicates the endpoint is not set up — both produce a warning rather than an incident, as does a non-JSON response body or a stale finishedAt. The warning event type is warning.application_health.

Implementation Examples ​

Laravel ​

php
Route::get('/health', function () {
    $checks = [
        ['name' => 'Database', 'status' => rescue(fn () => DB::connection()->getPdo() ? 'ok' : 'failed', 'crashed')],
        ['name' => 'Cache', 'status' => rescue(fn () => Cache::store()->get('health-check') !== false ? 'ok' : 'failed', 'crashed')],
        ['name' => 'Queue', 'status' => rescue(fn () => Queue::size('default') >= 0 ? 'ok' : 'failed', 'crashed')],
    ];

    return response()->json([
        'finishedAt' => now()->toIso8601String(),
        'checkResults' => $checks,
    ]);
});

Express (Node.js) ​

javascript
app.get('/health', async (req, res) => {
  const checkResults = [
    { name: 'Database', status: (await checkDatabase()) ? 'ok' : 'failed' },
    { name: 'Cache', status: (await checkRedis()) ? 'ok' : 'failed' },
    { name: 'Queue', status: (await checkQueue()) ? 'ok' : 'failed' },
  ];

  res.json({
    finishedAt: new Date().toISOString(),
    checkResults,
  });
});

Best Practices ​

  • Keep the health endpoint lightweight -- avoid expensive queries or external calls that could time out
  • Use timeouts on each component check to prevent the health endpoint itself from hanging
  • Protect the endpoint with the health check secret — Hoppla sends it as the oh-dear-health-check-secret header, so your endpoint can reject requests that lack it
  • Return a check per subsystem (one checkResults entry each) so you can quickly identify which component is failing