Short answer: For a scheduled property-management import, use a free heartbeat monitor only after the importer validates and commits its result; pair it with an HTTP check for reachability, and page on freshness rather than process liveness.
A free uptime API is useful only when it can prove that the import produced the right result. The least complex design is a small Go heartbeat endpoint that records success after validation, plus an external monitor that alerts when the heartbeat stops.
The page an on-call engineer sees should say: nightly-lease-import has not reported a valid result for 47 minutes; last accepted run: 02:13 UTC; expected next run: 02:30 UTC. That is more actionable than a green web server check. It identifies the missing evidence and the change that may need review.
An importer can return HTTP 200 while loading zero units, accepting stale files, or writing half a portfolio. Reachability is not freshness.
What should the alert prove?
Start with the business event and work backwards to the signal. Emit a success heartbeat only after the source, schema, row count, transaction, and timestamp checks have passed. If a portfolio normally contains 8,000 to 12,000 units, 42 rows deserves investigation even when the process exits successfully. Those bounds need historical data and a review path for acquisitions; they should be versioned with the importer so a rollback restores the validation policy too.
The external monitor needs an opaque job name, a run identifier, a completion time, and a count. It does not need lease data. Sending that dataset to a monitoring service creates a second data-governance boundary.
That boundary is the trade-off.
A hosted monitor can reduce on-call work, but it may provide less control over retention, notification routing, or regional execution than a self-hosted check. A self-hosted check keeps data and failure modes closer to the importer, while making its own scheduler and alert delivery part of the SLO. Neither is universally safer.
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net/http"
"time"
)
type ImportResult struct {
Job string `json:"job"`
RunID string `json:"run_id"`
Rows int `json:"rows"`
Schema string `json:"schema"`
Completed string `json:"completed_at"`
}
func accepted(r ImportResult, previous time.Time) bool {
if r.Job != "nightly-lease-import" || r.Rows < 8000 || r.Rows > 12000 || r.Schema != "lease-v3" {
return false
}
t, err := time.Parse(time.RFC3339, r.Completed)
return err == nil && t.After(previous)
}
func fingerprint(runID string) string {
h := sha256.Sum256([]byte(runID))
return hex.EncodeToString(h[:])
}
func heartbeat(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var r ImportResult
if json.NewDecoder(req.Body).Decode(&r) != nil || !accepted(r, time.Now().Add(-24*time.Hour)) {
http.Error(w, "invalid import result", http.StatusUnprocessableEntity)
return
}
json.NewEncoder(w).Encode(map[string]string{"accepted_run": fingerprint(r.RunID)})
}
func main() {
http.HandleFunc("/internal/import-heartbeat", heartbeat)
http.ListenAndServe(":8080", nil)
}
This is a minimal illustration, not a persistence layer. In production, previous belongs in durable state, the endpoint needs authentication, and duplicate run IDs must be idempotent. The key boundary is after commit and validation, never at process start.
How should developers choose free uptime monitoring APIs?
“Which free uptime monitoring API should I use?” hides three signals. A heartbeat monitor checks that a job reported completion. An HTTP monitor checks that an endpoint responds. A log-based signal checks that an event exists and can be correlated. They can coexist, but they do not provide the same rollback evidence.
Healthchecks.io documents a job-ping model with a schedule and grace period. That maps to a missed completion, but it cannot infer semantic correctness unless the importer validates before pinging. UptimeRobot documents HTTP(s), keyword, ping, port, and other monitor types. That breadth helps service reachability; a raw HTTP check still says little about a committed import. Better Uptime documents uptime checks and incident notification around monitored endpoints. Its boundary is the same when the endpoint reports liveness instead of a validated domain event.
These are differences in signal shape, not a ranking. A heartbeat service is a poor fit when the requirement is a full transaction audit; an HTTP monitor is a poor fit when the endpoint stays healthy while the scheduled job is stale. The practical limitation is that no external check can recover semantic evidence the importer never emitted. Free plans, request limits, retention, notification policy, and APIs can change, so verify current documentation before adoption and record the review date. Do not turn a volatile plan detail into an architectural assumption.
| Signal | It can establish | It cannot establish alone | Rollback use |
|---|---|---|---|
| Job heartbeat | A validated job reported before its deadline | Every record is correct | Missed-run detection |
| HTTP check | An endpoint returned the expected response | The import committed | Control-plane reachability |
| Log event | A structured event was emitted and correlated | The event reflects durable state | Diagnosis and audit |
Use a service-level objective for the workflow, not only the endpoint. For example, an objective might say that 99.5% of scheduled imports finish with an accepted result inside the agreed window over a rolling period. The business owns the target; the monitor must measure the event that the objective names.
How do you stop a late alert from causing a bad rollback?
A missed heartbeat is evidence of missing freshness, not proof that the newest deployment is broken. The alert should include the last accepted run, expected deadline, deployed revision, and whether the source delivered a file. That lets a responder separate scheduler delay, upstream delivery failure, and application regression.
I would keep three timestamps: scheduled time, source-observed time, and commit time. They answer different questions. If source-observed time is current but commit time is old, the importer is suspect. If both are old, rolling back application code may be an expensive response to an upstream delay.
Thresholds need a grace period. A 30-minute schedule does not imply a page at minute 31 when source delivery commonly varies by several minutes. Base the grace period on observed timing and the SLO error budget, then review it when the schedule or portfolio changes. Too short creates false pages; too long lets stale rent or occupancy data influence operations.
The operational trace is short:
- Record a run ID when the importer starts.
- Validate source, schema, row count, and transaction outcome.
- Emit one idempotent heartbeat after commit.
- Wait through the configured grace period.
- Link the alert to the last accepted run.
- Compare the deployed revision with the failure boundary before reverting.
This gives testing a precise contract. A unit test can reject an empty portfolio, an integration test can verify commit-before-heartbeat ordering, and a failure-injection test can terminate the process between commit and notification. External notification may fail after the transaction succeeds; retrying the heartbeat must not repeat the import.
What should the Go service expose?
Keep the public health endpoint separate from internal import evidence. /healthz can answer whether the process serves requests. The heartbeat endpoint should authenticate its caller and accept only fields needed to evaluate freshness. Store the accepted event in the system that owns import state, then export redacted telemetry for correlation.
OpenTelemetry's signal model separates logs, metrics, and traces. A structured log can carry job, run_id, rows, revision, and completed_at; a metric can express age since the last accepted result; a trace can connect source retrieval, validation, and commit. Do not put tenant names or lease details into an external ping URL.
Page on age of the last accepted result, with a separate warning for unusual row count. A single “up” number compresses too much information. Capacity planning belongs in review: estimate heartbeat volume from scheduled jobs, frequency, retries, and tests. Keep stable labels such as job and environment on metrics; put run-specific identifiers in logs and traces to avoid unbounded metric series.
The practical choice
For a property import, choose the free monitoring API whose signal, retention, authentication, and export behavior fit the rollback procedure. A job-ping service is a boundary for missed completion. An HTTP monitor is a boundary for reachability. Structured telemetry is evidence for explaining a late or partial run. None repairs weak import validation.
The least risky implementation is narrow: validate the result, commit it, emit an authenticated heartbeat, and alert on freshness with a measured grace period. Treat the external monitor as an independent witness. When it pages, inspect the evidence chain before reverting a release.
A green endpoint is not a fresh property dataset. The rollback decision should follow the accepted business event.
Top comments (0)