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
/healthservice 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
encoreCLI 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
- Log in to Vigilmon → Add Monitor → HTTP.
- URL:
- Encore Cloud:
https://yourapp-production.encr.app/health - Self-hosted:
https://yourapp.example.com/health
- Encore Cloud:
- Check interval: 60 seconds.
- Response timeout: 10 seconds.
- Expected status code:
200. - 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. - 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
- In Vigilmon → Status Pages → Create.
- Add your production service monitors and cron job heartbeat monitors.
- Configure a custom domain (
status.example.com) or use the Vigilmon-provided subdomain. - 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.