Production APIs go down in unexpected ways: a misconfigured deployment, a database connection pool exhausted at 2 AM, a goroutine leak that silently starves your workers. Vigilmon catches these before your customers do. In this tutorial you'll wire a Go Gin API into Vigilmon for uptime monitoring, heartbeat pings, and alert routing — all in about 20 minutes.
What You'll Build
- A
/healthendpoint in Gin that returns JSON status - A Vigilmon HTTP monitor pointed at your API
- A background goroutine heartbeat using
time.Ticker - Graceful shutdown that doesn't lose in-flight requests
- Email and Slack webhook alerts
Prerequisites
- Go 1.21+
- A running Gin project (or follow along from scratch)
- A free Vigilmon account
Step 1: Add the Health Endpoint
A health endpoint lets Vigilmon (and your load balancer) confirm your service is alive and ready to serve traffic. The key is to return a meaningful status, not just 200 OK.
// internal/health/handler.go
package health
import (
"database/sql"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type Handler struct {
DB *sql.DB
}
type HealthResponse struct {
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
Checks map[string]string `json:"checks"`
}
func (h *Handler) Check(c *gin.Context) {
checks := map[string]string{}
overallStatus := "ok"
// Ping the database with a 2-second deadline
if err := h.DB.PingContext(c.Request.Context()); err != nil {
checks["database"] = "error: " + err.Error()
overallStatus = "degraded"
} else {
checks["database"] = "ok"
}
statusCode := http.StatusOK
if overallStatus != "ok" {
statusCode = http.StatusServiceUnavailable
}
c.JSON(statusCode, HealthResponse{
Status: overallStatus,
Timestamp: time.Now().UTC(),
Checks: checks,
})
}
Register it on your router:
// main.go (excerpt)
r := gin.Default()
healthHandler := &health.Handler{DB: db}
r.GET("/health", healthHandler.Check)
Test it locally:
curl -s localhost:8080/health | jq
# {
# "status": "ok",
# "timestamp": "2025-06-29T10:00:00Z",
# "checks": { "database": "ok" }
# }
When your database is unreachable, the endpoint returns 503 — Vigilmon treats any non-2xx response as a failure and immediately starts the alerting chain.
Step 2: Create a Vigilmon HTTP Monitor
Log in to Vigilmon and create a new HTTP Monitor:
| Field | Value |
|---|---|
| URL | https://api.yourdomain.com/health |
| Method | GET |
| Check interval | 60 seconds |
| Expected status | 200 |
| Timeout | 10 seconds |
| Regions | Select 2–3 for triangulation |
Under Alert Channels, add your email address. You'll add Slack in Step 5.
Click Save — Vigilmon immediately begins polling. Within a minute you'll see the first green tick on your dashboard.
Pro tip: If your API requires authentication, add an Authorization header in the monitor config. Create a dedicated read-only monitoring token rather than reusing a user token.
Step 3: Goroutine Heartbeat with time.Ticker
HTTP uptime checks confirm the process is responding, but they don't tell you whether your background workers are healthy. The heartbeat pattern fixes this: your worker pings Vigilmon every N seconds to confirm it's alive. If pings stop, Vigilmon alerts you.
First, grab your Heartbeat URL from Vigilmon (Dashboard → Heartbeat Monitors → New). It looks like:
https://vigilmon.online/api/heartbeats/YOUR-UUID/ping
Now wire it into your worker:
// internal/worker/heartbeat.go
package worker
import (
"context"
"log"
"net/http"
"time"
)
const heartbeatInterval = 60 * time.Second
func RunWithHeartbeat(ctx context.Context, heartbeatURL string, work func(ctx context.Context) error) {
ticker := time.NewTicker(heartbeatInterval)
defer ticker.Stop()
httpClient := &http.Client{Timeout: 5 * time.Second}
ping := func() {
resp, err := httpClient.Get(heartbeatURL)
if err != nil {
log.Printf("[heartbeat] ping failed: %v", err)
return
}
resp.Body.Close()
log.Printf("[heartbeat] pinged, status %d", resp.StatusCode)
}
// Ping immediately on start so Vigilmon knows the worker is up
ping()
for {
select {
case <-ctx.Done():
log.Println("[heartbeat] worker shutting down")
return
case <-ticker.C:
// Do the actual work first, then ping on success
if err := work(ctx); err != nil {
log.Printf("[worker] error: %v", err)
// Don't ping — let Vigilmon detect the silence
continue
}
ping()
}
}
}
Launch it from main.go:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go worker.RunWithHeartbeat(ctx, os.Getenv("VIGILMON_HEARTBEAT_URL"), processQueue)
Store the heartbeat URL in an environment variable — never hard-code it:
VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeats/YOUR-UUID/ping
Vigilmon's heartbeat monitor expects a ping within a configurable window (e.g. 90 seconds). If the window expires without a ping — because the worker panicked, the goroutine deadlocked, or the process restarted — you get alerted immediately.
Step 4: Graceful Shutdown
Monitoring is only useful if your deployment process doesn't generate false alerts. A non-graceful shutdown kills in-flight requests mid-response, triggers 5xx errors during the check interval, and floods your alert inbox during every deploy.
// main.go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// ... register routes ...
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
// Start server in background
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %v", err)
}
}()
// Block until signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down...")
// Give in-flight requests 30 seconds to complete
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("forced shutdown: %v", err)
}
log.Println("server exited cleanly")
}
Pair this with a Vigilmon maintenance window during planned deployments: the dashboard lets you mute alerts for a set duration so a rolling restart doesn't page your on-call.
Step 5: Alert Routing
Email Alerts
Already configured in Step 2. Vigilmon sends alerts when:
- The endpoint returns a non-2xx status
- The endpoint doesn't respond within the timeout
- The heartbeat window expires without a ping
You can configure escalation policies — first alert goes to your personal email, if unacknowledged for 15 minutes it escalates to the team channel.
Slack Webhook Alerts
- Create a Slack incoming webhook in your workspace (Slack docs)
- In Vigilmon → Alert Channels → Add Channel → Webhook
- Paste the Slack webhook URL
- Assign the channel to your monitors
The alert payload Vigilmon posts looks like:
{
"text": "🔴 *api.yourdomain.com/health* is DOWN\nStatus: 503 | Region: eu-west-1\nDuration: 2m 14s"
}
You can also send to PagerDuty, Microsoft Teams, or any webhook-capable endpoint.
Step 6: Test the Full Loop
- Simulate a DB failure: stop your local database and curl
/health— you should see503. - Verify Vigilmon detects it: within one check interval, your Vigilmon dashboard shows the monitor red and an alert fires.
- Test the heartbeat gap: stop your worker process and wait for the heartbeat window to expire — you should get an alert.
- Recover: bring the DB back up. Vigilmon auto-recovers and sends a "back online" notification.
Production Checklist
- [ ]
/healthchecks all critical dependencies (DB, cache, message broker) - [ ] Heartbeat URL stored in environment variable, not code
- [ ] Graceful shutdown with
SIGTERMhandling - [ ] Alert channels tested end-to-end
- [ ] Maintenance windows configured for your deploy pipeline
- [ ] Monitor check interval tuned to your SLA (60s for most APIs)
Wrapping Up
You now have a production-grade monitoring setup for your Go Gin API:
- Uptime monitoring that catches HTTP failures within 60 seconds
- Heartbeat monitoring that catches silent worker failures
- Alert routing to email and Slack with escalation
Sign up for Vigilmon and get your first monitor running in under five minutes — no credit card required.
If you hit issues or have questions, drop a comment below. Happy monitoring!