Heartbeat examples
Each example runs a job called backup, then calls a Heartbeat Monitor’s URL. Replace both: the job with yours, and https://api.uup.dev/hb/your-token with the Heartbeat URL on your Monitor’s Checks card.
Every example keeps two rules:
- Call on success only. The call comes after the job, and runs only when the job succeeded. A job that failed stays silent, and the Monitor goes down once its Grace runs out.
- Wait a short time. Each call gives up after 10 seconds, so a slow answer from us never holds your job. The
curlline, the one the Checks card shows, also tries again up to 3 times, so a short break on our side does not fail the call.
Keep the URL secret: anyone who has it can call it. In a repository, keep it in your platform’s secrets rather than in the file.
To choose how often the job must call, and how late it may be, see Heartbeat Monitors.
crontab
Section titled “crontab”Add the call to the job’s own line, after &&, so it runs only when the job succeeded:
0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 https://api.uup.dev/hb/your-tokenGive the Monitor the same schedule: choose Custom cron, and enter the line’s five fields.
systemd timers
Section titled “systemd timers”A timer starts a service. Make the call the service’s ExecStartPost=, which runs only once every ExecStart= has succeeded:
[Unit]Description=Nightly backup
[Service]Type=oneshotExecStart=/usr/local/bin/backup.sh# Runs only once ExecStart has succeeded.ExecStartPost=/usr/bin/curl -fsS -m 10 --retry 3 https://api.uup.dev/hb/your-token
# /etc/systemd/system/backup.timer[Unit]Description=Run the nightly backup
[Timer]OnCalendar=*-*-* 03:00:00Persistent=true
[Install]WantedBy=timers.targetStart the timer with systemctl enable --now backup.timer.
GitHub Actions
Section titled “GitHub Actions”A step runs only when every step before it succeeded, so make the call the last step:
name: Nightly backupon: schedule: - cron: "0 3 * * *"jobs: backup: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: ./backup.sh # Runs only when every step above succeeded. - run: curl -fsS -m 10 --retry 3 https://api.uup.dev/hb/your-tokenIn a public repository, store the URL as a secret, and write ${{ secrets.UUP_HEARTBEAT_URL }} in its place. A workflow’s cron is read in UTC. Unless your Project’s timezone is UTC, write the Monitor’s schedule with the same times in the Project’s timezone.
Kubernetes CronJobs
Section titled “Kubernetes CronJobs”The container runs the job and then the call, in one shell command. Its image needs curl. With restartPolicy: Never, a failed call does not run the job a second time.
apiVersion: batch/v1kind: CronJobmetadata: name: backupspec: schedule: "0 3 * * *" jobTemplate: spec: template: spec: restartPolicy: Never containers: - name: backup image: registry.example.com/backup:latest command: - /bin/sh - -c - ./backup.sh && curl -fsS -m 10 --retry 3 https://api.uup.dev/hb/your-tokenIn a script, join the job and the call with &&:
./backup.sh && curl -fsS -m 10 --retry 3 https://api.uup.dev/hb/your-tokenIn a script that runs with set -e, the call can simply be its last line: the script stops at the first command that fails.
fetch is built into Node, and AbortSignal.timeout makes it give up. The example uses await outside a function, so save it as a .mjs file:
// Throws when the backup fails, so uup is never called.await backup();
await fetch("https://api.uup.dev/hb/your-token", { signal: AbortSignal.timeout(10000) });Python
Section titled “Python”urllib.request is in the standard library, so nothing needs installing:
import urllib.request
# Raises when the backup fails, so uup is never called.backup()
urllib.request.urlopen("https://api.uup.dev/hb/your-token", timeout=10)package main
import ( "log" "net/http" "time")
func main() { if err := backup(); err != nil { log.Fatal(err) // The backup failed, so uup is never called. }
client := &http.Client{Timeout: 10 * time.Second} response, err := client.Get("https://api.uup.dev/hb/your-token") if err != nil { log.Fatal(err) } response.Body.Close()}net/http is in the standard library:
require "net/http"
# Raises when the backup fails, so uup is never called.backup
uri = URI("https://api.uup.dev/hb/your-token")Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 10) do |http| http.get(uri.path)endfile_get_contents calls a URL when allow_url_fopen is on, as it is by default:
<?php
// Throws when the backup fails, so uup is never called.backup();
file_get_contents("https://api.uup.dev/hb/your-token", false, stream_context_create([ "http" => ["timeout" => 10],]));