Rippler docs

Integrations

Paste-ready snippets for crontab, shell, Python, Node.js and GitHub Actions.

Every snippet here is complete enough to paste as-is. Replace <pingID> with your monitor's own — the app shows these same snippets with it already filled in.

Nothing below is required: one curl to the ping URL when a job finishes is a perfectly good integration. These add two things worth having:

  • A run id (rid), so the ping that closes a run is paired with the start/ that opened it, rather than with whatever event happened to come last. A job that sometimes overlaps itself measures wrong without one.
  • A duration, timed by the job. The server can only see when the pings arrived, which includes network time and retry backoff.

About the curl flags

Every shell example uses the same set, and each one is there for a reason:

-f            treat a non-2xx response as an error
-s -S         hide the progress meter, keep the errors
-m 10         bound the request, so a hanging ping cannot wedge the job
--retry 5     ride out a blip
-o /dev/null  the response body is not interesting

Crontab

Goes in your own crontab, via crontab -e.

0 3 * * * curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/start/?rid=$$"; if /path/to/your-job.sh; then curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/?rid=$$"; else curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/fail/?rid=$$"; fi

Two details that are easy to get wrong and matter:

  • The start ping is separated from the job by ; rather than &&, so a problem reaching Rippler can never stop your job from running. Monitoring that can take the job down with it is worse than no monitoring.
  • The if/else means a failed success ping is not mistaken for a failed job.
  • $$ is the shell's process id, which makes a serviceable run id.

/etc/crontab takes a user column after the schedule. Paste this there without one and cron reads curl as the username, rejects the file, and ignores every job in it. See troubleshooting.

Shell

Reports a crash immediately rather than waiting for the ping to be missed, and times the run itself.

#!/bin/bash
set -o pipefail

rid=$(uuidgen 2>/dev/null || echo "$$-$(date +%s)")
started=$(date +%s)

curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/start/?rid=$rid"

/path/to/your-job.sh
code=$?

duration=$(( $(date +%s) - started ))

if [ $code -eq 0 ]; then
  curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/?rid=$rid&duration=$duration"
else
  curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/fail/?rid=$rid&duration=$duration"
fi

exit $code

The exit $code at the end matters: the wrapper passes the job's own exit status through, so anything watching the script still sees what it expects.

Python

import time, uuid, requests

PING_URL = "https://api.rippler.io/ping/<pingID>/"
rid = str(uuid.uuid4())
started = time.monotonic()

requests.get(PING_URL + "start/", params={"rid": rid}, timeout=10)

path = "fail/"

try:
    do_the_work()
    path = ""
finally:
    requests.get(
        PING_URL + path,
        params={"rid": rid, "duration": round(time.monotonic() - started, 3)},
        timeout=10,
    )

The path starts as fail/ and is only cleared once the work returns, so a raised exception reports a failure without needing to catch it — and the finally means the ping is sent either way.

time.monotonic() rather than time.time(), so a clock adjustment mid-run cannot produce a negative duration.

Node.js

const PING_URL = 'https://api.rippler.io/ping/<pingID>/';
const rid = crypto.randomUUID();
const started = Date.now();

const ping = (path = '', params = {}) =>
	fetch(`${PING_URL}${path}?${new URLSearchParams({ rid, ...params })}`, {
		signal: AbortSignal.timeout(10_000),
	}).catch(() => {});

await ping('start/');

try {
	await doTheWork();
	await ping('', { duration: (Date.now() - started) / 1000 });
} catch (error) {
	await ping('fail/', { duration: (Date.now() - started) / 1000 });
	throw error;
}

The .catch(() => {}) on the ping is deliberate, and is the same idea as the ; in the crontab line: a failed ping must not become a failed job. The throw error at the end preserves the original failure.

GitHub Actions

The first step goes at the top of the job, the other two at the end. A failing workflow reports the failure rather than going quiet.

env:
  RIPPLER_RID: ${{ github.run_id }}-${{ github.run_attempt }}

- name: Tell Rippler the job started
  run: curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/start/?rid=$RIPPLER_RID"

# … your build steps …

- name: Report success to Rippler
  if: success()
  run: curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/?rid=$RIPPLER_RID"

- name: Report failure to Rippler
  if: failure()
  run: curl -fsS -m 10 --retry 5 -o /dev/null "https://api.rippler.io/ping/<pingID>/fail/?rid=$RIPPLER_RID"

Including run_attempt in the run id means a re-run is a distinct run rather than a second closing ping for the first one.

Anything else

There is no SDK to install and nothing to import. A ping is an HTTP request to a URL with no authentication, so any language that can make one can report a job — see Pings for the URLs and their parameters.

If your jobs already live in a crontab, rippler sync will wire all of them up at once rather than doing this by hand.

On this page