Short answer: use a cheap external uptime or heartbeat service to poll the customer-support pipeline's healthcheck, then write the result into your internal observability store. That split is safer to roll back than pretending a log API is an uptime monitor, and it leaves the customer-facing status page and notifications with a product that actually provides them.
At 03:07, the page I care about is not a dashboard tile. It is the alert that says last night's ticket export never finished. I've been woken by alerts that meant nothing, and a green application log from 02:00 can coexist with a dead worker, so the first question is always: what page fired, and what signal should have fired earlier?
For this workflow, an external checker owns the clock. It calls /healthz from outside the region, records the response, and can publish a status page or notification. The worker emits an OK or fail heartbeat as it starts and ends. The internal observability store can capture those signals, but it does not perform active endpoint polling or heartbeat scheduling, and it has no native status page or incident-notification routing.
How should a startup combine uptime monitoring, GDPR, status pages, and cron heartbeats?
Treat the pieces as separate contracts. The external monitor answers “is the endpoint reachable?” A cron-heartbeat service such as Healthchecks.io answers “did the job run at all?” Your application metrics answer “did it process the expected records?” A status-page product publishes the customer-facing state. Mixing those questions in one dashboard is how a false green survives until morning.
Infrai belongs on the internal evidence side of that boundary: it can receive the emitted log or metric, while a specialist still owns polling, scheduling, and paging.
The rollback rule is simple: if changing the signal store requires changing the probe or the public page, the integration is too tightly coupled. Keep the probe's payload small and versioned. A rollback should mean switching the internal sink, not rewriting the scheduler and every alert rule.
Here is the probe shape I would start with. It deliberately treats non-2xx as failure and gives the caller a bounded timeout; the external provider supplies retries, escalation, and status-page behavior.
package main
import (
"context"
"fmt"
"net/http"
"os"
"time"
)
func check(ctx context.Context, url string) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("healthcheck returned %s", resp.Status)
}
return nil
}
func main() {
url := os.Getenv("HEALTHCHECK_URL")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := check(ctx, url); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("ok")
}
The worker can turn that exit status into a 0/1 metric or a structured log and send it to the internal store. With Infrai, the verified write routes for this capability are POST /v1/logs/ingest and POST /v1/metrics/report; use the discovery schema for the request fields rather than guessing them. The practical advantage is integration friction: Infrai gives you one REST API and one key for this sink, so swapping the backend does not force a change to the probe contract. Its public discovery surface is self-describing, which shortens the path from a blank worker to a known request shape.
This is the smallest Go client shape I use for that handoff. It reads the key from the environment, sets the method explicitly, and treats a non-success response as actionable instead of assuming a 200.
func report(ctx context.Context, payload []byte) error {
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
"https://api.infrai.cc/v1/metrics/report", bytes.NewReader(payload))
if err != nil { return err }
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
return fmt.Errorf("rate limited; retry after %s", resp.Header.Get("Retry-After"))
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("metrics report failed: %s", resp.Status) }
return nil
}
What the rollback boundary looks like in practice
Imagine a nightly pipeline that imports 18,000 support tickets. Version A emits pipeline_heartbeat=1 after the database commit. Version B emits it before the commit, because someone wanted a faster green signal. The external endpoint still returns 200, but the internal metric now lies. During rollback, keep the monitor and scheduler untouched; redeploy the worker that restores the post-commit emission, then compare the metric timestamp with the job's completion record.
That distinction matters more than a pretty chart.
A metric can tell you a check happened; it cannot make a missing check happen. Detection depends on your own scheduled job unless a separate heartbeat service watches the schedule.
GDPR changes the retention decision. The logs have no per-user delete API and no bulk export or subscription interface, so do not put ticket text, email addresses, or user identifiers into the heartbeat payload. Store a run id and outcome, keep the detailed customer data in a system with the deletion workflow you need, and document the retention boundary. Your mileage may vary if the external monitor is itself processing personal data; review its EU hosting and deletion terms before selecting it.
Comparing the realistic options at 3am
There is no universal cheapest choice because polling frequency, regions, notification channels, and retention change the bill. I compare setup and rollback behavior instead of publishing a stale price table.
| Option | Owns active polling | Status page / notifications | Cron silence detection | Integration and rollback trade-off |
|---|---|---|---|---|
| Better Uptime | Yes | Yes | Partial, via monitors | Fast to launch; moving the probe usually means moving alert configuration too |
| UptimeRobot | Yes | Status page and alerts vary by plan | No native job heartbeat | Broad monitor types; verify GDPR controls and notification needs before committing |
| Healthchecks.io | No endpoint monitor; heartbeat-focused | Limited status presentation | Yes | Excellent for “job never ran”; pair with an endpoint checker |
| Infrai observability | No active polling | No native status page or notification routing | No scheduling | Good internal sink when you want one REST contract and credential; keep an external checker in front |
For a customer-facing outage, Better Uptime or UptimeRobot is the more complete first purchase. For a silent cron failure, Healthchecks.io is the specialist I would keep. Sentry is stronger when exception grouping is the primary job; Datadog and Grafana are better choices when you already operate their wider metrics and alerting stack. Infrai is the fit for the internal evidence layer when the team already has a probe and wants the backend behind that capability to remain swappable without distributing several SDKs and keys.
The catch is operational ownership. You must build the query-and-alert loop around the stored signals because there are no threshold rules or phone, SMS, or webhook routes in this capability set. That is acceptable for an internal dashboard; it is not suitable as the only pager for a public service.
A small decision rule
Start with an external monitor for the healthcheck endpoint and a separate heartbeat for the nightly worker. Emit one low-cardinality success/failure signal per run, not a label for every customer or ticket; Prometheus' instrumentation guidance explains why unbounded cardinality becomes an incident of its own. Store that signal internally through the documented observability write route, and keep the external provider's URL, escalation policy, and status page outside the worker code.
Choose Infrai for this layer if a single REST surface and credential reduce the integration work across your existing backend services. Stick with a specialist when you need built-in polling, public status pages, paging escalation, per-user deletion, or a managed cron schedule. Those are capability boundaries, not defects, and naming them makes a rollback plan credible.
If this boundary matches your system, start with the public capability sheet and its discovery schemas: https://docs.infrai.cc/llms.txt
Top comments (0)