tutorial

Monitoring Encore Applications with Vigilmon: External Uptime, Health Checks & Heartbeat Monitoring

Practical guide to adding external uptime monitoring to Encore Go and TypeScript applications — health endpoints, Vigilmon HTTP monitors, cron job heartbeats, and webhook alerting for services deployed through Encore Cloud or self-hosted.

Encore is a backend framework for Go and TypeScript that lets you define infrastructure — APIs, databases, queues, cron jobs, secrets — directly in code. Encore provisions the underlying infrastructure whether you're running locally in development or deploying to production on Encore Cloud. The framework handles routing, service discovery, distributed tracing, and metrics dashboards out of the box. What Encore's own observability stack does not cover is the external user perspective: is your API reachable from outside your infrastructure right now, from multiple locations around the world? Vigilmon answers that. It sits entirely outside your cloud environment, probes your endpoints from multiple geographic regions, and alerts you the moment a service becomes unavailable — regardless of what your Encore Cloud dashboard reports.

This tutorial focuses on the Encore Go SDK. For the TypeScript SDK, see our Encore.ts monitoring guide.

What You'll Build

  • A /health service in an Encore Go application
  • A Vigilmon HTTP monitor with response-body assertions
  • A heartbeat monitor for Encore Go cron jobs
  • A webhook receiver for DOWN/UP Vigilmon events
  • A public status page for your Encore API

Prerequisites

  • Go 1.21+ with the encore CLI installed (go install encore.dev/cli/cmd/encore@latest)
  • An Encore Go application
  • A free account at vigilmon.online

Step 1: Add a Health Service

In Encore, every API group is a service defined by a package. Create a dedicated health package:

// health/health.go
package health

import (
    "context"
    "database/sql"

    "encore.dev/beta/auth"
    "encore.dev/rlog"
)

// HealthResponse is returned by the health check endpoint.
type HealthResponse struct {
    Status  string            `json:"status"`
    Checks  map[string]string `json:"checks"`
    Service string            `json:"service"`
}

// Check is an unauthenticated health endpoint exposed at GET /health.
//
//encore:api public method=GET path=/health
func Check(ctx context.Context) (*HealthResponse, error) {
    checks := map[string]string{}
    ok := true

    // Check database connectivity (replace with your actual Encore SQL database)
    if err := pingDatabase(ctx); err != nil {
        checks["database"] = "error: " + err.Error()
        ok = false
        rlog.Error("health: database check failed", "err", err)
    } else {
        checks["database"] = "ok"
    }

    // Check an upstream dependency
    if err := pingUpstream(ctx); err != nil {
        checks["upstream_api"] = "error: " + err.Error()
        ok = false
        rlog.Error("health: upstream check failed", "err", err)
    } else {
        checks["upstream_api"] = "ok"
    }

    status := "ok"
    if !ok {
        status = "degraded"
    }

    return &HealthResponse{
        Status:  status,
        Checks:  checks,
        Service: "health",
    }, nil
}

func pingDatabase(ctx context.Context) error {
    // Replace with your Encore SQLDatabase query
    // return db.QueryRow(ctx, "SELECT 1").Scan(new(int))
    return nil
}

func pingUpstream(ctx context.Context) error {
    // Replace with an HTTP call to a critical upstream service
    return nil
}

Encore's //encore:api public directive marks the endpoint as unauthenticated, so Vigilmon can reach it without credentials. The method=GET path=/health sets the HTTP binding.

Test locally:

encore run
curl http://localhost:4000/health
{
  "status": "ok",
  "checks": { "database": "ok", "upstream_api": "ok" },
  "service": "health"
}

To return HTTP 503 when degraded, return an *errs.Error from the handler:

import "encore.dev/beta/errs"

// At the end of Check():
if !ok {
    return nil, errs.B().Code(errs.Unavailable).Msg("service degraded").Err()
}

With a 503, Vigilmon detects the failure purely from the HTTP status code. Without it, use a keyword assertion on "status":"ok" (see Step 2).


Step 2: Configure Vigilmon HTTP Monitor

  1. Log in to VigilmonAdd Monitor → HTTP.
  2. URL:
    • Encore Cloud: https://yourapp-production.encr.app/health
    • Self-hosted: https://yourapp.example.com/health
  3. Check interval: 60 seconds.
  4. Response timeout: 10 seconds.
  5. Expected status code: 200.
  6. Keyword assertion: "status":"ok" — Vigilmon marks the check as failed if this string is absent, catching the degraded state even when Encore returns HTTP 200.
  7. Click Save.

Vigilmon probes from multiple geographic regions simultaneously. It requires a quorum of probe locations to agree on a failure before alerting — this eliminates false positives from transient regional routing issues.


Step 3: Webhook Receiver for Vigilmon Events

Add an Encore endpoint to receive Vigilmon DOWN/UP webhook notifications:

// health/webhook.go
package health

import (
    "context"
    "os"

    "encore.dev/rlog"
)

// VigilmonPayload is the webhook payload sent by Vigilmon.
type VigilmonPayload struct {
    MonitorName  string `json:"monitor_name"`
    Status       string `json:"status"` // "down" or "up"
    URL          string `json:"url"`
    ResponseCode int    `json:"response_code"`
    CheckedAt    string `json:"checked_at"`
}

// WebhookReceived confirms receipt to Vigilmon.
type WebhookReceived struct {
    Received bool `json:"received"`
}

// VigilmonWebhook receives uptime events from Vigilmon.
//
//encore:api public method=POST path=/webhook/vigilmon
func VigilmonWebhook(ctx context.Context, payload *VigilmonPayload) (*WebhookReceived, error) {
    if payload.Status == "down" {
        rlog.Error("Vigilmon DOWN",
            "monitor", payload.MonitorName,
            "url", payload.URL,
            "code", payload.ResponseCode,
            "at", payload.CheckedAt,
        )
        if slackURL := os.Getenv("SLACK_WEBHOOK_URL"); slackURL != "" {
            notifySlack(ctx, slackURL, payload)
        }
    } else {
        rlog.Info("Vigilmon UP", "monitor", payload.MonitorName)
    }
    return &WebhookReceived{Received: true}, nil
}

func notifySlack(ctx context.Context, webhookURL string, p *VigilmonPayload) {
    // send a POST to webhookURL with the alert details
}

In Vigilmon → Alerts → Add channel → Webhook, point to https://yourapp.example.com/webhook/vigilmon.


Step 4: Heartbeat Monitor for Encore Cron Jobs

Encore's Go SDK includes a built-in cron package. Use a Vigilmon heartbeat to detect when a cron job silently stops running.

// jobs/cleanup.go
package jobs

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "time"

    "encore.dev/cron"
    "encore.dev/rlog"
)

// Encore registers this as a cron job that runs every hour.
var _ = cron.NewJob("hourly-cleanup", cron.JobConfig{
    Title:    "Hourly data cleanup",
    Every:    1 * cron.Hour,
    Endpoint: RunCleanup,
})

//encore:api private
func RunCleanup(ctx context.Context) error {
    cleaned, err := performCleanup(ctx)
    if err != nil {
        return fmt.Errorf("cleanup failed: %w", err)
    }
    rlog.Info("cleanup complete", "records", cleaned)

    // Ping Vigilmon heartbeat on success
    pingHeartbeat()
    return nil
}

func performCleanup(ctx context.Context) (int, error) {
    // Your cleanup logic
    return 0, nil
}

func pingHeartbeat() {
    url := os.Getenv("VIGILMON_HEARTBEAT_URL")
    if url == "" {
        return
    }
    client := &http.Client{Timeout: 5 * time.Second}
    resp, err := client.Post(url, "application/json", nil)
    if err != nil {
        rlog.Warn("heartbeat ping failed", "err", err)
        return
    }
    resp.Body.Close()
}

In Vigilmon, create a Heartbeat monitor with an expected interval of 65 minutes (5 minutes longer than the cron schedule to account for execution time variance). If the cron job stops running, the heartbeat lapses and Vigilmon fires an alert.

Store the heartbeat URL in Encore's secret manager:

encore secret set --type production VIGILMON_HEARTBEAT_URL

Step 5: Alert Routing Strategy

Encore Cloud's dashboard covers deployment metrics, distributed traces, and per-service logs. Vigilmon covers the external availability question that Encore Cloud cannot answer from inside your infrastructure.

| Failure mode | Source | Route to | |---|---|---| | Encore service crash or panic | Encore Cloud | PagerDuty + Slack #eng-incidents | | Deployment failure | Encore Cloud | On-call rotation | | External endpoint unreachable | Vigilmon | PagerDuty + Slack #prod-alerts | | Cron job missed heartbeat | Vigilmon | Slack #infra-ops-critical | | TLS certificate expiring | Vigilmon | DevOps team |


Encore Cloud vs Self-Hosted

| Concern | Encore Cloud | Self-hosted | |---|---|---| | Health URL | https://yourapp-production.encr.app/health | https://yourapp.example.com/health | | Deployment observability | Built-in Encore Cloud dashboard | Your own APM / logging | | Secrets | encore secret set | Your own secret manager | | Vigilmon scope | External, user-perspective uptime | Same — Vigilmon is always external |

Encore Cloud's dashboards tell you about internal service health. Vigilmon tells you what happens when a user's request leaves the public internet and hits your service — whether DNS resolves, TLS handshakes succeed, and the health check passes.


Step 6: Public Status Page

  1. In Vigilmon → Status Pages → Create.
  2. Add your production service monitors and cron job heartbeat monitors.
  3. Configure a custom domain (status.example.com) or use the Vigilmon-provided subdomain.
  4. Add a status badge to your API documentation or README:
<img src="https://vigilmon.online/badge/<monitor-id>.svg" alt="API Status" />

What Vigilmon Adds to an Encore Application

| Capability | Encore Cloud | Vigilmon | |---|---|---| | Service request traces | Yes | No | | Per-service metrics | Yes | No | | Cron job visibility | Yes | No | | Infrastructure provisioning | Yes | No | | External synthetic HTTP probing | No | Yes | | DNS resolution failure detection | No | Yes | | TLS/SSL certificate expiry alerts | No | Yes | | Scheduled job heartbeat monitoring | No | Yes | | Multi-region uptime probing | No | Yes | | Public status page | No | Yes |


Encore removes infrastructure boilerplate and gives you built-in observability for everything inside your cloud environment. Vigilmon removes the monitoring blind spot that every hosted observability platform shares: it can only see what happens inside your infrastructure, not what a user outside it actually experiences. Together, they give a small Go team the monitoring coverage of a much larger organization.

Start monitoring your Encore application from the outside — register free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →