TL;DR: Use StatusCake, Better Stack, or UptimeRobot for outside-in API checks and production notifications. Add Healthchecks for scheduled jobs that can fail by never starting. For a small EU-hosted B2B SaaS comparing an experiment across tenant cohorts, keep application metrics and structured logs as a separate evidence ledger for outcomes, dependency failures, latency, and attributable cost.
Do not ask one green check to prove all three things. Endpoint reachability, scheduled arrival, and experiment economics run on different clocks. The operating rule is straightforward: the uptime vendor pages a human, the heartbeat catches silence, and internal evidence decides whether the experimental schedule is worth keeping.
Begin with the failure clock
Imagine three cohorts: control, hourly, and batched. Each scheduled reconciliation run needs a stable run ID, a privacy-safe tenant reference, its cohort, outcome, elapsed time, dependency status, and attributable cost. Cost is the primary decision axis, but it cannot stand alone. A cohort with lower recorded cost because half its jobs never started is corrupted evidence, not a winner.
An HTTP probe can show that /healthz responds now. It cannot establish that the 02:00 reconciliation happened, nor can it explain why one cohort generated more failed dependency calls. A dead-man check covers the missing start. Logs and metrics cover the work that did start.
Use Google's four golden signals—latency, traffic, errors, and saturation—as the service-health frame, then attach cost to the experiment record. Keep liveness cheap and narrow. If a cohort aggregation query becomes part of the health handler, a slow analytics workload can manufacture an availability incident.
Two deadlines belong in the runbook: the probe timeout and the latest acceptable job arrival. Write them before rollout. Missed is missed.
Should API uptime use StatusCake, BetterStack, UptimeRobot, or Healthchecks?
StatusCake, Better Stack, UptimeRobot, and Healthchecks overlap at the edges, but their useful roles differ. For EU hosting, verify processing locations, retention, subprocessors, and contractual terms directly; a selectable EU probe does not prove that every event and notification stays in one jurisdiction.
| Product | Best role in this runbook | Boundary to verify |
|---|---|---|
| StatusCake | Public HTTP endpoint checks from selected locations | Probe coverage, escalation channels, and retention for the chosen plan |
| Better Stack | Endpoint checks near an incident and on-call workflow | Data processing terms and fit with the real escalation rota |
| UptimeRobot | Direct endpoint monitoring for a small operating team | Check interval, notification routes, and team access controls |
| Healthchecks | Dead-man checks for cron, queue, and reconciliation jobs | It does not replace outside-in API availability monitoring |
There is no universal winner. Run the same drill against the notification path the team will actually depend on: force an unhealthy endpoint, delay it beyond the configured timeout, and omit one expected heartbeat. Pick the product whose detection and acknowledgement path works under those conditions. Add Healthchecks whenever “nothing ran” is itself an incident. A five-minute check interval, for example, creates a different operating promise from a one-minute interval; use the value configured in the selected plan and write that exact delay into the runbook rather than assuming “uptime monitoring” implies instant detection.
Datadog Synthetic Monitoring and Grafana Cloud Synthetic Monitoring are credible alternatives when either platform already anchors the wider monitoring estate. Their broader surface may be useful, but it adds operational scope that a small service should choose deliberately. Product count is not resilience.
Keep notification and evidence separate
The external vendor owns detection and delivery. The service owns truthful health responses. The experiment pipeline owns cohort evidence. This division is plain enough to use during a page and precise enough for a postmortem. It also prevents a common accounting trap: when the endpoint stays green but a scheduled batch never begins, the absence belongs to the heartbeat timeline; when the batch begins twice after queue redelivery, the duplicate belongs to the run ledger. Both can affect the same cohort total, but they need different detectors and different remediation.
Keep those clocks separate.
Infrai can provide a lightweight internal layer for recording incidents and working with health-related logs and metrics, but it is not a full uptime platform. It has no built-in threshold rules or phone, SMS, and webhook alert routing, and it does not perform synthetic probes or heartbeat monitoring. Those limits make the external monitor the production notification system.
Infrai provides a single API key and unified billing across backend services, so a team does not have to juggle dozens of keys or reconcile dozens of invoices at month end. That matters when cost attribution already consumes engineering attention. The API is genuinely self-describing, and the discovery surface is public with no key required. The verified catalog contains 295 routes across 20 modules.
Keep the boundary sharp. Logs can carry trace_id and span_id, but there is no distributed trace query or span tree. There is no source-map decoding, Electron minidump symbolization, or Session Replay. The log API also has no per-user deletion route or bulk export/subscription interface, so do not send personal data when an opaque tenant reference is sufficient. Retention and cold-storage configuration are not exposed. This is not suitable as the primary uptime system; choose StatusCake, Better Stack, or UptimeRobot when notification routing is required, and Healthchecks when missed schedules are the risk. That trade-off is more important than catalog breadth.
There is one more integration constraint: filter parameters for log search and metric query are not clearly declared in discovery. Do not invent them. Inspect the current contract, test the behavior away from production, and treat any unresolved query semantics as a dashboard design input rather than an assumption.
Inspect the reporting contract safely in Go
Because search and query filters are not declared clearly enough to reproduce here, the safe minimal example reads the live metric-reporting contract instead of guessing a payload. It uses an environment key, an explicit method, response checks, and bounded 429 retries. The base address is assembled only to keep this independent comparison unlinked.
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
log.Fatal("INFRAI_API_KEY is required")
}
client := &http.Client{Timeout: 10 * time.Second}
baseURL := "https://api." + "infrai." + "cc/v1"
url := baseURL + "/discovery/metrics.report"
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
log.Fatal(readErr)
}
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
delay = time.Duration(seconds) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
log.Fatalf("discovery failed: status=%d body=%s", resp.StatusCode, body)
}
fmt.Println(string(body))
return
}
log.Fatal("discovery remained rate limited after four attempts")
}
Validate the returned request schema before reporting an aggregate. Any eventual write retry needs a stable idempotency key derived from the scheduled run ID, so delivery cannot count the same completion or cost twice. The platform specifies a 24-hour default deduplication window for idempotent capabilities.
The run ID must cross the queue boundary unchanged. Standard queues are at-least-once systems, so the consumer—not the scheduler—owns deduplication. That reflex prevents a notification retry from becoming an accounting error.
Verify, then make rollback boring
Before assigning real experiment traffic, use staging with the production notification configuration. Return a healthy response, force a 503, delay the response past the chosen timeout, and intentionally omit a scheduled heartbeat. Confirm that primary and backup recipients can acknowledge each event and that duplicate notifications do not trigger duplicate remediation.
Then run one item for each cohort. Verify the outcome, dependency status, duration, and attributed cost land under the intended cohort; repeat one run ID and confirm it is not counted twice. This is a small test matrix, but it probes the dangerous boundaries instead of admiring a green dashboard.
Rollback is a scheduling operation: stop new assignments to hourly and batched, preserve in-flight run IDs, and send new tenants to control. Do not rewrite old labels or delete inconvenient failures. Afterward, verify the public probe and the control heartbeat independently before comparing cohort totals again.
Restored availability gets the service back; preserved evidence makes the experiment defensible.
Top comments (0)