package api import ( "database/sql" "encoding/json" "log" "net/http" "time" ) // amPayload mirrors the Alertmanager webhook v4 payload. type amPayload struct { Version string `json:"version"` Status string `json:"status"` Alerts []amAlert `json:"alerts"` } type amAlert struct { Status string `json:"status"` Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` StartsAt time.Time `json:"startsAt"` EndsAt time.Time `json:"endsAt"` GeneratorURL string `json:"generatorURL"` Fingerprint string `json:"fingerprint"` } func handleAlertmanagerWebhook(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var payload amPayload if err := decodeJSON(r, &payload); err != nil { respond(w, http.StatusBadRequest, errResp("invalid payload")) return } now := time.Now().Unix() for _, a := range payload.Alerts { name := a.Labels["alertname"] labelsJSON, _ := json.Marshal(a.Labels) annotationsJSON, _ := json.Marshal(a.Annotations) // Alertmanager uses zero time ("0001-01-01T00:00:00Z") to mean "still firing". var endsAtUnix *int64 if a.EndsAt.Year() > 1 { t := a.EndsAt.Unix() endsAtUnix = &t } _, err := db.ExecContext(r.Context(), ` INSERT INTO alerts (fingerprint, name, status, labels, annotations, starts_at, ends_at, generator_url, received_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(fingerprint) DO UPDATE SET status = excluded.status, labels = excluded.labels, annotations = excluded.annotations, ends_at = excluded.ends_at, generator_url = excluded.generator_url, received_at = excluded.received_at`, a.Fingerprint, name, a.Status, string(labelsJSON), string(annotationsJSON), a.StartsAt.Unix(), endsAtUnix, a.GeneratorURL, now, ) if err != nil { log.Printf("upsert alert %s: %v", a.Fingerprint, err) } } w.WriteHeader(http.StatusOK) } }