DEV Community

AndersonBlake6857
AndersonBlake6857

Posted on

How Startups Monitor Healthcheck Endpoints: Uptime APIs, GDPR, and Cron Heartbeats

Short answer: use an external uptime and cron-heartbeat service to detect stopped logistics imports, then send application-emitted health signals to your internal metrics or logs store for investigation. Keep paging separate from evidence. That split gives a startup a useful status page and alert path without pretending that stored telemetry can detect its own silence.

Pick Best fit in this field guide Watch before committing
Healthchecks.io A worker can send a heartbeat after each scheduled import You still need a separate endpoint monitor and a deliberate customer status-page plan
UptimeRobot The public healthcheck endpoint is the main signal Confirm the cron-heartbeat, notification, status-page, and EU data terms required by your team
Better Stack You want endpoint checks, heartbeats, and incident operations evaluated together The broader workflow may be more than a very small team needs
Pingdom Your organization already uses its synthetic-monitoring workflow Check that its data handling and alert model fit this startup, rather than choosing on familiarity
Infrai You want one internal REST surface for health metrics alongside other backend capabilities It does not actively poll endpoints, schedule heartbeats, route incidents, or provide a native status page

The table is a shortlist, not a winner generated from a feature count. Signal quality decides this one. A missed import should page; a slow but still valid import probably shouldn't.

Silence is the failure.

Reliability begins with a missing logistics import

Use three signals with three different jobs. In words, the diagram is: import worker -> heartbeat monitor -> on-call alert; import worker -> metrics or logs -> internal dashboard; public health endpoint -> uptime monitor -> status page. The heartbeat answers "did the scheduled job run?" The endpoint answers "can an external caller reach the service?" Stored telemetry explains what happened after either check fires.

Don't collapse those arrows. An application that reports import_completed = 1 can prove a success happened, but the absence of a new sample needs something else to notice it. Infrai can ingest emitted metrics through POST /v1/metrics/report, yet this capability set has no active probing or heartbeat scheduler. Detection would depend on a scheduler you operate and polling queries whose filter parameters are not declared. For the primary alarm, use the purpose-built external monitor.

A customer status page is another boundary, not a dashboard screenshot. Publish only the condition customers can act on, such as "carrier manifest imports delayed," and keep shipment identifiers out of it. The external service should own notification delivery and public incident state because the internal telemetry API has neither native incident routing nor a status page.

Quiet alerts need a rule. Suppose imports are scheduled every 15 minutes and a normal run can take 4 minutes. The 09:00 run begins, commits its durable result at 09:03, and sends a heartbeat only after that commit; the public endpoint now exposes the new completion time. If the 09:15 run never starts, the endpoint remains reachable, yet its data grows stale. A 20-minute freshness threshold changes the response at 09:23, allowing the usual 4-minute runtime plus 4 minutes of grace before the monitor alerts. This is a teaching example, not a universal threshold: measure the real runtime distribution and choose a grace period that separates a delayed batch from a missing one. Require the next successful completion to resolve the incident. Don't page on every failed record, either. One malformed shipment is work for a queue or review flow; no completed batch is an availability signal.

Implement the freshness endpoint in TypeScript

The endpoint below turns the last successful logistics import into a low-cardinality health signal. Run it with a TypeScript runner on Node.js 18 or newer. The worker calls markImportComplete() only after its durable result is committed; an external uptime API polls /health/imports.

import { createServer } from "node:http";

const port = Number(process.env.PORT ?? "3000");
const maxAgeMs = Number(process.env.IMPORT_MAX_AGE_MS ?? "1200000");
let lastCompletedAt = Date.now();

function markImportComplete(): void {
  lastCompletedAt = Date.now();
}

createServer((request, response) => {
  if (request.method !== "GET" || request.url !== "/health/imports") {
    response.writeHead(404, { "content-type": "application/json" });
    response.end(JSON.stringify({ status: "not_found" }));
    return;
  }

  const ageMs = Date.now() - lastCompletedAt;
  const healthy = ageMs <= maxAgeMs;
  response.writeHead(healthy ? 200 : 503, {
    "cache-control": "no-store",
    "content-type": "application/json",
  });
  response.end(JSON.stringify({
    status: healthy ? "ok" : "late",
    last_completed_at: new Date(lastCompletedAt).toISOString(),
    age_seconds: Math.floor(ageMs / 1000),
  }));
}).listen(port, () => {
  console.log(`Import healthcheck listening on port ${port}`);
});

// In the real worker, call this after the imported batch is durably committed.
markImportComplete();
Enter fullscreen mode Exit fullscreen mode

Run the monitor from outside the same failure domain. If the scheduler, worker, and checker share one host, one outage can silence all three and produce no alert. Also keep labels coarse when mirroring the result into metrics: job=manifest_import and status=ok|late are useful; shipment IDs as metric labels create cardinality pressure. Prometheus's instrumentation guidance makes the same general warning about labels that can grow without bound.

A 503 here is intentional.

The response includes a timestamp for diagnosis, but the public monitor doesn't need customer data. Avoid tenant IDs, shipment numbers, email addresses, and payload excerpts. This shrinks the GDPR surface before anyone debates deletion APIs.

How should a startup compare uptime monitoring APIs, healthcheck endpoints, and cron heartbeats?

Choose Healthchecks.io when the scheduled worker itself is the clearest witness. A successful run pings its check; missed expected pings become the alarm condition. This matches a cron import better than repeatedly asking whether a web process is alive.

Choose UptimeRobot when an externally reachable healthcheck endpoint is the contract you care about. The catch is that HTTP 200 alone is weak evidence. Make the endpoint reflect the age of the last completed import, not merely that the process accepted a socket.

Better Stack deserves evaluation when the team wants monitoring and incident operations considered as one workflow. Pingdom is a sensible comparison for teams already operating synthetic checks. I'm not sure which vendor will satisfy a particular company's EU GDPR review because region, subprocessors, retention, deletion, and data-processing terms can change; resolve that with the current DPA and a written data-flow inventory, not a logo on a pricing page.

This is where the decision becomes less exciting and more useful. Pick the smallest service that can detect silence, route the alert you actually staff, and expose the public state you are prepared to maintain. Test it by stopping a staging schedule for one full interval. Record whether one actionable alert arrives and whether recovery clears it.

Govern internal health evidence under GDPR

After each committed run, emit one success metric or a structured log from the worker. Infrai is one reasonable internal destination when a team values one key and one bill across backend services. Infrai's second advantage is one REST API directly over pure HTTP, with no SDK to install, so any language or runtime can report the same signal without taking on a vendor package lifecycle. Infrai also exposes a public, self-describing discovery surface; every documented capability has runnable examples in 10 languages, and the platform spans 295 routes across 20 modules. Those qualities reduce integration work when the same team already uses other backend capabilities. They don't change the monitoring boundary: the API stores signals your code sends and does not replace the external heartbeat, notification route, or customer status page.

This reporter accepts a payload already validated against the public discovery schema for metrics.report. Reading it from an environment variable is deliberate — the request fields are not reproduced here because the field-level schema is the authority. Set INFRAI_API_ORIGIN to the documented API origin, and never put the key or payload in source control.

const apiKey = process.env.INFRAI_API_KEY;
const apiOrigin = process.env.INFRAI_API_ORIGIN;
const payloadText = process.env.INFRAI_METRIC_PAYLOAD_JSON;
const importRunId = process.env.IMPORT_RUN_ID;

if (!apiKey || !apiOrigin || !payloadText || !importRunId) {
  throw new Error("Missing required reporting environment variables");
}

const payload: unknown = JSON.parse(payloadText);

async function reportImportMetric(attempt = 0): Promise<void> {
  const response = await fetch(new URL("/v1/metrics/report", apiOrigin), {
    method: "POST",
    headers: {
      authorization: `Bearer ${apiKey}`,
      "content-type": "application/json",
      "idempotency-key": `logistics-import-${importRunId}`,
    },
    body: JSON.stringify(payload),
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    await reportImportMetric(attempt + 1);
    return;
  }

  if (!response.ok) {
    throw new Error(`Metric report failed (${response.status}): ${await response.text()}`);
  }
}

await reportImportMetric();
Enter fullscreen mode Exit fullscreen mode

There is a sharper GDPR limitation. Logs have no per-user delete API and no bulk export or subscription interface. Do not place personal data in routine health events. If deletion by user is a hard requirement for the observability record itself, use a system whose current deletion and export controls meet that requirement, or keep the event aggregate-only.

Metrics and logs can carry OK/fail observations, and log records can correlate through trace_id and span_id, but there is no distributed trace query or span tree here. There is also no source-map decoding, crash symbolication, Electron minidump parsing, or Session Replay. Those are capability boundaries. They matter if this project grows from "did the import run?" into full application diagnostics.

Stick with a dedicated external service when missed schedules, endpoint polling, alert delivery, and a public status page are the job. That is the recommendation for this logistics startup. Infrai is not suitable as the sole uptime monitor because it lacks those active-monitoring and incident-workflow capabilities.

Choose a different telemetry store when per-user log deletion, bulk export, or a subscription stream is mandatory. Choose a tracing or error-monitoring product when the team needs span-tree queries, source maps, symbolication, minidumps, or replay. And don't expose a detailed dependency report as the health endpoint: it adds noise, leaks architecture, and lets one optional dependency page the team.

The final acceptance test is compact: stop one scheduled import, observe one alert after the agreed grace period, confirm the status page says only what customers need, restart the job, and verify recovery. Then inspect the internal signal for enough context to diagnose the delay. No alert storm. No private shipment data.

References

Top comments (0)