Quickstart
Create a monitor, copy its ping URL, and call that URL when the job finishes. A missing ping is the incident. These examples use a placeholder. The real URL is on the monitor page and looks like https://nightshiftwatchdog.com/ping/YOUR_TOKEN. Treat it like a password.
- Create an account and choose New monitor. Set an interval or a 5-field cron expression, plus a grace period for jitter.
- Copy the ping URL. For cron and GitHub Actions, put it in NIGHTSHIFT_PING_URL.
- POST it when the work succeeds. POST /start when a run begins (that does not move the deadline) and /fail when it errors.
JSON fields are optional. Send tokens and cost_usd from an agent loop so a spike can trip a spend alert on Pro or Team. duration_ms is optional too. The same fields work as query parameters on a GET.
curl
POST when the job finishes. Add tokens and cost when you have them.
curl -fsS -m 10 -X POST "https://nightshiftwatchdog.com/ping/YOUR_TOKEN" \
-H "content-type: application/json" \
-d '{"tokens":1234,"cost_usd":0.012}'cron
Ping after the command succeeds, and /fail if it does not. Match the monitor's schedule to this crontab.
# crontab -e
0 * * * * /usr/local/bin/nightly-job && curl -fsS -m 10 -X POST "$NIGHTSHIFT_PING_URL" -H "content-type: application/json" -d '{"tokens":0,"cost_usd":0}' || curl -fsS -m 10 -X POST "$NIGHTSHIFT_PING_URL/fail"n8n
Add an HTTP Request node at the end of the workflow, and an error branch that calls /fail.
Method: POST
URL: https://nightshiftwatchdog.com/ping/YOUR_TOKEN
Body content type: JSON
JSON body:
{
"tokens": {{$json.tokens}},
"cost_usd": {{$json.cost_usd}}
}
On error, POST https://nightshiftwatchdog.com/ping/YOUR_TOKEN/failMake
HTTP > Make a request, as the last module. Use the Break error handler to POST /fail.
URL: https://nightshiftwatchdog.com/ping/YOUR_TOKEN
Method: POST
Body type: Raw
Content type: application/json
Request content:
{"tokens": {{tokens}}, "cost_usd": {{cost}}}Zapier
Webhooks by Zapier > POST. Put it after the steps that do the work.
URL: https://nightshiftwatchdog.com/ping/YOUR_TOKEN
Payload type: json
Data:
tokens: (number, optional)
cost_usd: (number, optional)GitHub Actions
Store the ping URL as a secret. The finish step runs even when the job fails.
- name: Tell Nightshift the job started
run: curl -fsS -m 10 -X POST "$NIGHTSHIFT_PING_URL/start"
env:
NIGHTSHIFT_PING_URL: ${{ secrets.NIGHTSHIFT_PING_URL }}
- name: Tell Nightshift the job finished
if: always()
env:
NIGHTSHIFT_PING_URL: ${{ secrets.NIGHTSHIFT_PING_URL }}
run: |
if [ "${{ job.status }}" = "success" ]; then
curl -fsS -m 10 -X POST "$NIGHTSHIFT_PING_URL"
else
curl -fsS -m 10 -X POST "$NIGHTSHIFT_PING_URL/fail"
fiVercel cron
Call this route from vercel.json. Ping after the work, and /fail if it throws.
export async function GET() {
const ping = process.env.NIGHTSHIFT_PING_URL!;
try {
await doTheWork();
await fetch(ping, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ tokens: 0, cost_usd: 0 }),
});
return Response.json({ ok: true });
} catch (error) {
await fetch(ping + "/fail", { method: "POST" });
throw error;
}
}Python loop
Ping once per iteration so a stuck loop trips the schedule, and a spike trips runaway detection.
import os, requests
URL = os.environ["NIGHTSHIFT_PING_URL"]
while True:
try:
result = run_once()
requests.post(
URL,
json={"tokens": result.tokens, "cost_usd": result.cost_usd},
timeout=5,
)
except Exception:
requests.post(URL + "/fail", timeout=5)
raiseNode loop
Same idea as the Python loop. A missing ping is the signal, so do not swallow the error before /fail.
const url = process.env.NIGHTSHIFT_PING_URL;
while (true) {
try {
const result = await runOnce();
await fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ tokens: result.tokens, cost_usd: result.costUsd }),
});
} catch (error) {
await fetch(url + "/fail", { method: "POST" });
throw error;
}
}Claude Code
A Stop hook pings when a Claude Code session ends. Loops should still ping from inside the agent.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "curl -fsS -m 10 -X POST \"$NIGHTSHIFT_PING_URL\" -H \"content-type: application/json\" -d \"{\\\"tokens\\\":0,\\\"cost_usd\\\":0}\" >/dev/null || curl -fsS -m 10 -X POST \"$NIGHTSHIFT_PING_URL/fail\" >/dev/null || true"
}
]
}
]
}
}More detail
- Getting startedCreate a monitor, ping it once, and confirm the dashboard sees the run.
- Ping APIGET and POST /ping/<token>, plus /start and /fail. Optional tokens and cost_usd.
- Schedules and graceIntervals, 5-field cron, timezones, and what the minute checker can and cannot see.
- Runaway limitsHard caps and baseline spikes for runs, tokens, and dollars.
- AlertsEmail, Slack, Discord, and Telegram. What is encrypted, and when a message is sent.
- BillingStripe Checkout, the customer portal, webhooks, and what each plan enforces.