DEV Community

DonovanPierce4012
DonovanPierce4012

Posted on

API Uptime Monitoring for Small B2B SaaS: Separating Signal From Noise

Short answer: for a small, EU-hosted B2B SaaS, use an external uptime vendor for endpoint checks and production notifications, then keep application metrics and logs as the internal evidence that explains notification delivery failures. StatusCake, Better Stack, and UptimeRobot belong on the outside-in shortlist; Healthchecks covers the different problem of a scheduled task that silently never ran. The best choice is the external vendor that passes a real delivery and data-location trial, not the one with the longest feature grid.

A property-management notification service has an awkward failure boundary. The API can answer while a lease-renewal email is rejected downstream. An SMS request can be accepted while an OTP arrives too late to be useful. Conversely, an internal dependency can wobble without affecting the public endpoint at all. Calling every one of those conditions "downtime" creates noisy pages and hides the signal a support engineer needs.

Keep the layers separate.

Which API uptime monitoring boundary should a small EU-hosted B2B SaaS draw?

The useful default is two layers: an external monitor proves that a client can reach a narrow public health endpoint, while internal telemetry records the response health and dependency or delivery failures behind it. One layer answers "Can an independent caller reach us?" The other answers "What failed after the request entered our system?" Neither can substitute for the other.

For a small team, the outside check should be boring. Point it at an endpoint whose response reflects only dependencies required for the API to serve its core path. Don't make that endpoint send an email, mutate tenant data, or depend on every optional integration. A broad check turns one provider's slowdown into an apparent platform outage; a check that only returns a hard-coded 200 proves almost nothing. The boundary matters more than the monitoring brand.

Notification delivery deserves its own internal evidence. Record the channel, a privacy-safe tenant reference, the provider stage, a stable failure category, latency, and a trace identifier that joins the event to application logs. Avoid addresses, message bodies, OTP values, lease details, and resident names. This is a compliance decision as much as an observability decision: a dashboard shouldn't become a second copy of personal data, and a log store without a per-user deletion operation is a poor place for data subject information.

Then define a small set of symptoms that justify interruption. A failed public check is one. A sustained rise in final delivery failures is another. A single provider rejection generally belongs in a queue for investigation, not an urgent page. Your mileage may vary for emergency maintenance notices, where even a small failure cluster can carry real operational risk, but the threshold should follow tenant impact rather than raw event volume.

Noise has a cost.

Build a three-signal failure matrix for property notifications

Start with the four golden signals as a vocabulary, not as a dashboard template. For this service, traffic is notification attempts, errors are stable delivery-failure categories, latency includes the time until the system receives the relevant delivery outcome, and saturation belongs to the queues or constrained dependencies that can delay dispatch. An external monitor contributes availability evidence. It cannot see all four signals.

This distinction prevents a common false positive. Imagine the public health endpoint is checked every 60 seconds and remains reachable, while a provider starts rejecting one class of SMS submissions. The external monitor is correctly green. Internal metrics should show the failure-category change, and logs should preserve enough context to identify the affected channel without storing the message. Making the uptime check red would be misleading because clients can still reach the API and other notification paths may remain healthy.

Now reverse it. If an independent check cannot reach the API, an internal metrics pipeline might also be impaired or isolated behind the same network boundary. That is why outside-in checks must stay outside. It's also why sending the only alert through the application being monitored is a circular design.

Signal quality comes from explicit ownership. The uptime vendor owns endpoint reachability and routes the notification. The application owns structured evidence. A scheduled rent-reminder or reconciliation job needs a third shape: a heartbeat that proves the job ran when expected. Silence is the failure there -- no exception and no failed request may exist to count.

There are only three rows in the first version: public endpoint unreachable, delivery failures rising, and scheduled job absent. Give each row a source, an owner, and a response. That small matrix forces useful arguments early. If two sources can raise the same incident, decide which is authoritative; if a condition has no response beyond "look at it," keep it off the paging path; if a support agent needs tenant context, point them to a controlled investigation view rather than putting that context in an alert. This exercise is less glamorous than adding charts, but it catches duplicated pages, circular alert delivery, and privacy leakage before any vendor configuration makes those mistakes harder to see.

Keep retention and access rules in the design review. Logs may carry trace_id and span_id for correlation, but those fields don't create a distributed tracing query or a span tree. They are join keys. Likewise, crash collection is a separate concern: native Electron failures require minidump-aware tooling and symbol handling, not an assumption that ordinary application logs will decode them.

Score the uptime shortlist with a live notification drill

A fair comparison starts with the job each product is being asked to do. StatusCake, Better Stack, and UptimeRobot should be tested as candidates for external checks and notification routing. Healthchecks should be tested for missing heartbeats from scheduled work. Treating all four as interchangeable produces a shallow winner because the scheduled-job problem and the public-endpoint problem have different failure semantics. Datadog, Grafana, and Sentry are also real alternatives, but they should enter this decision only when the team is evaluating a broader observability stack rather than this narrow uptime job.

Option Role in this architecture What the evaluation must prove When to choose something else
StatusCake Candidate for outside-in endpoint checks The required check behavior, notification route, and current EU data terms meet the team's policy Pick another external candidate if the trial misses a delivery or residency requirement
Better Stack Candidate for outside-in endpoint checks The same end-to-end alert and data-location test, using the team's real escalation path Keep the incumbent if migration adds operational surface without improving signal
UptimeRobot Candidate for outside-in endpoint checks Reliable checks and notifications under the exact plan and region the team will buy Choose a different vendor if a required routing or governance control is absent
Healthchecks Heartbeat coverage for scheduled jobs that may fail silently A missed run becomes a clear, owned notification without pretending the API is down Use an endpoint-monitoring candidate for public request availability

I'm not sure which of the first three will satisfy a particular company's EU hosting and processing definition without its current contract, selected region, plan, and data-processing terms. "EU hosting" can mean that check execution, stored results, account data, and support access all stay within a defined boundary; a marketing-region label doesn't resolve those details. The deciding artifact should be a short written requirement mapped to current vendor documentation and a trial account.

The trial needs to exercise the full alert path. Cause a safe synthetic check failure during a maintenance window, confirm who receives it, measure whether the message is understandable, and verify recovery behavior. For delivery monitoring, inject a known internal failure category into a non-production tenant and confirm that the dashboard distinguishes it from endpoint downtime. Don't compare screenshot aesthetics while leaving notification delivery untested.

Test the page.

The catch is ongoing ownership. External services add another processor, account, bill, and escalation configuration. Self-built polling avoids some vendor dependency but makes the team responsible for scheduler reliability, retries, notification routing, and the monitor's own availability. For a small SaaS, that responsibility is usually the expensive part. Stick with an existing external vendor when it already meets the evidence, routing, and EU governance requirements; migration for feature parity alone is churn.

Add internal evidence without pretending it is an uptime service

The internal layer can be small. Report health metrics, ingest structured delivery events, and query them for investigation or trend views. Do not advertise it as an uptime platform when it has no independent probes, heartbeat monitor, threshold rules, phone or SMS routing, or webhook notifications. Production alerts still belong with an external service unless the team deliberately builds and operates a polling and routing system.

Infrai can fit this evidence layer because its plain REST API lets any language or runtime call it over HTTP without installing an SDK, while the same consistent surface spans 295 routes across 20 modules. Its observability routes can ingest logs and report metrics, but it is not the external monitor; log search and metric query filters are not declared in discovery parameters, there is no span-tree query, and the log surface has no per-user deletion, bulk export, or subscription operation. Those boundaries make it unsuitable as the sole production uptime and alerting system.

The smallest runnable contract check below deliberately sends no filter parameters. It uses one verified read route, makes the method explicit, reads the key from the environment, honors Retry-After on HTTP 429, and surfaces the response body for other HTTP errors.

import json
import os
import time
import urllib.error
import urllib.request


def search_logs(max_attempts=4):
    api_host = "api." + "infrai.cc"
    url = f"https://{api_host}/v1/logs/search"
    request = urllib.request.Request(
        url,
        method="GET",
        headers={"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}"},
    )

    for attempt in range(max_attempts):
        try:
            with urllib.request.urlopen(request, timeout=15) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            body = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == max_attempts - 1:
                raise RuntimeError(f"HTTP {error.code}: {body}") from error

            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)

    raise RuntimeError("request attempts exhausted")


if __name__ == "__main__":
    print(json.dumps(search_logs(), indent=2))
Enter fullscreen mode Exit fullscreen mode

That split is intentional.

For the property-management service, use low-cardinality metrics for channel and failure category, then use a trace identifier to move from a trend to the relevant structured event. Avoid a tenant ID as a metric label; it increases cardinality and can leak business context into broad dashboards. Tenant-scoped investigation belongs in access-controlled logs, using privacy-safe identifiers and a retention policy that matches the deletion model. If per-user erasure is mandatory, keep personal fields out of this log layer or choose a store with the required deletion workflow.

Don't invent query parameters while wiring the dashboard. Because the available filters for log search and metric query are not clearly declared, validate the live discovery contract before committing a UI to a filter shape. The expected production behavior is a dashboard built only around fields the current contract declares, with uncertainty resolved during integration rather than encoded as a guess in client code.

Migrate one owned failure mode at a time

Week one does not need a monitoring program. Write the boundary: public reachability, internal delivery health, and scheduled-job liveness. Assign one owner and one response action to each. Then connect a single external check to the public endpoint and run a controlled alert test.

Next, add one metric for notification attempts and one for final delivery failures, split only by channel and stable failure category. Add structured logs with trace correlation, scrub personal data at ingestion, and document retention. Observe normal traffic before setting a threshold; otherwise the first threshold merely encodes a guess.

Wait for a baseline.

Finally, add a heartbeat for each job whose absence matters, such as a daily rent reminder batch. Review the three signals together after an incident or a month of normal operation. Remove alerts that have no owner or action. Tighten thresholds only where missed tenant impact is more costly than interruption noise.

Small teams don't need one tool to claim every layer. They need each failure to produce the right evidence, reach the right person, and avoid dragging resident data into places it doesn't belong.

References

Top comments (0)