Pulse Monitoring

Pulse checks implement a dead-man's-switch pattern: your service pings Emit Vision on a schedule, and if pings stop arriving for longer than expected, the check transitions through latedown and fires an alert.

This replaces external services like healthchecks.io for all fleet projects except emit-vision itself (see Watchdog policy below).

How it works

Each pulse check has five statuses:

StatusMeaning
newCreated but hasn't received its first ping yet
upReceiving pings on schedule
lateOverdue — past expected interval but within grace period
downMissed deadline — alert fires
pausedMonitoring suspended, pings accepted silently

A check fires a down alert once when transitioning to down, and a recovery alert once when it returns to up. No repeated alerts while the status is stable.

Creating a check

  1. Open your project in the Emit Vision dashboard
  2. Navigate to Pulse in the sidebar
  3. Click New Check
  4. Set the name (e.g., "backup-cron"), expected interval (how often your service pings), and grace period (extra time before alerting)
  5. Optionally configure a notification channel (Slack, webhook, email, PagerDuty)
  6. Click Create and copy the ping URL shown

Ping URL format

GET https://api.emitvision.com/v1/pulse/<slug>
Authorization: Bearer <your-ingest-key>

Both GET and POST are supported. The GET method works with minimal tools like wget or curl in sidecar containers.

Docker Compose sidecar

Add this service to your docker-compose.prod.yml as a shared (un-profiled) service — not under a blue/green slot profile, so exactly one instance pings regardless of active slot:

services:
  pulse-ping:
    image: alpine:3.20
    restart: unless-stopped
    environment:
      - EMIT_VISION_INGEST_KEY=${EMIT_VISION_INGEST_KEY}
      - PULSE_URL=https://api.emitvision.com/v1/pulse/my-check
      - PING_INTERVAL=300
    entrypoint: /bin/sh
    command:
      - -c
      - |
        while true; do
          wget -q --header="Authorization: Bearer $${EMIT_VISION_INGEST_KEY}" \
               -O /dev/null "$$PULSE_URL" || true
          sleep $$PING_INTERVAL
        done

Replace my-check with your check's slug and set PING_INTERVAL to match your check's expected interval in seconds.

In blue-green deployments, place this service outside any slot profile (like you would postgres or redis). If it runs inside a slot, you'll get double-pings from both slots or silence after a switch.

Explicit failure signaling

Use the /fail endpoint to immediately mark a check as down — useful for crash loops or failed cron jobs:

# Cron job pattern: ping on success, fail on error
./backup.sh && wget -qO /dev/null "$PULSE_URL" \
            || wget -qO /dev/null "$PULSE_URL/fail"

This transitions the check to down instantly without waiting for the silence timeout.

Cron job monitoring

Pulse works for periodic jobs too. Set the expected interval to your cron schedule's period plus some slack:

  • Cron runs every 5 minutes → set interval to 6 minutes, grace to 2 minutes
  • Cron runs hourly → set interval to 65 minutes, grace to 10 minutes

Ping at the end of each successful run. If the job hangs or crashes, the silence triggers the alert.

Migrating from healthchecks.io

For each project with an existing uptime-ping or dms-ping sidecar:

  1. Create a check in the Emit Vision dashboard matching your current ping interval
  2. Update the sidecar in docker-compose.prod.yml:
    • Change the URL from https://hc-ping.com/... to https://api.emitvision.com/v1/pulse/<slug>
    • Add the auth header: --header="Authorization: Bearer $EMIT_VISION_INGEST_KEY"
  3. Deploy and verify the check status flips to up in the Pulse dashboard
  4. Delete the healthchecks.io check once confirmed
  5. Remove the HEALTHCHECKS_IO_UUID env var from your compose file

This removes healthchecks.io as a provisioning dependency for the project.

Watchdog-of-the-watchdog

Emit Vision hosting the fleet's dead-man's switch creates a circular dependency: if emit-vision goes down, all silence detection stops.

Policy: Emit Vision itself keeps one external healthchecks.io check (its existing sidecar stays pointed there). Every other project migrates to pulse. This is a deliberate architectural exception — not an oversight.

Do not migrate emit-vision's own uptime sidecar to pulse.