Integration Examples
Ready-to-use code snippets for Node.js, Python, Go, shell scripts, CI/CD, and containers.
Copy-paste examples for integrating IonHour heartbeat pings into your services and pipelines. All examples use the public ping endpoint which requires no authentication.
Replace YOUR_TOKEN with the token from your check's settings.
Shell / Cron
Basic Crontab
# Run backup every hour, ping on success
0 * * * * /usr/local/bin/backup.sh && curl -sf https://app.failsignal.com/api/signals/ping/YOUR_TOKEN > /dev/null 2>&1
With Retry
# Retry the ping up to 3 times with 5-second delay between attempts
0 * * * * /usr/local/bin/backup.sh && curl -sf --retry 3 --retry-delay 5 --max-time 10 https://app.failsignal.com/api/signals/ping/YOUR_TOKEN > /dev/null 2>&1
With Payload
#!/bin/bash
START=$(date +%s%3N)
./my-job.sh
EXIT_CODE=$?
END=$(date +%s%3N)
DURATION=$((END - START))
if [ $EXIT_CODE -eq 0 ]; then
curl -sf -X POST https://app.failsignal.com/api/signals/ping/YOUR_TOKEN \
-H "Content-Type: application/json" \
-d "{\"duration\": $DURATION, \"exit_code\": $EXIT_CODE}" > /dev/null 2>&1
fi
Node.js
Simple (Built-in fetch)
async function runJob() {
// ... your job logic ...
await fetch('https://app.failsignal.com/api/signals/ping/YOUR_TOKEN');
}
With Payload and Error Handling
async function runJob() {
const start = Date.now();
// ... your job logic ...
const duration = Date.now() - start;
try {
await fetch('https://app.failsignal.com/api/signals/ping/YOUR_TOKEN', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ duration, rows_processed: 1500 }),
signal: AbortSignal.timeout(10_000),
});
} catch {
// Ping failure shouldn't crash your job
}
}
Express Middleware (Health Check Heartbeat)
const FAILSIGNAL_TOKEN = process.env.FAILSIGNAL_TOKEN;
const PING_INTERVAL = 5 * 60 * 1000; // 5 minutes
setInterval(async () => {
try {
await fetch(
`https://app.failsignal.com/api/signals/ping/${FAILSIGNAL_TOKEN}`,
{ signal: AbortSignal.timeout(10_000) }
);
} catch {
console.error('IonHour ping failed');
}
}, PING_INTERVAL);
Python
Simple
import requests
def run_job():
# ... your job logic ...
requests.get(
"https://app.failsignal.com/api/signals/ping/YOUR_TOKEN",
timeout=10,
)
With Payload
import requests
import time
def run_job():
start = time.time()
# ... your job logic ...
duration_ms = int((time.time() - start) * 1000)
try:
requests.post(
"https://app.failsignal.com/api/signals/ping/YOUR_TOKEN",
json={"duration": duration_ms, "status": "completed"},
timeout=10,
)
except requests.RequestException:
pass # Don't let ping failure crash your job
Django Management Command
from django.core.management.base import BaseCommand
import requests
class Command(BaseCommand):
help = "Run nightly cleanup and ping IonHour"
def handle(self, *args, **options):
# ... your cleanup logic ...
requests.get(
"https://app.failsignal.com/api/signals/ping/YOUR_TOKEN",
timeout=10,
)
self.stdout.write(self.style.SUCCESS("Cleanup complete"))
Go
Simple
package main
import (
"net/http"
"time"
)
func pingIonHour(token string) {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://app.failsignal.com/api/signals/ping/" + token)
if err != nil {
return // Don't let ping failure crash your job
}
defer resp.Body.Close()
}
func main() {
// ... your job logic ...
pingIonHour("YOUR_TOKEN")
}
With Payload
package main
import (
"bytes"
"encoding/json"
"net/http"
"time"
)
func pingWithPayload(token string, payload map[string]interface{}) {
body, _ := json.Marshal(payload)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Post(
"https://app.failsignal.com/api/signals/ping/"+token,
"application/json",
bytes.NewBuffer(body),
)
if err != nil {
return
}
defer resp.Body.Close()
}
func main() {
start := time.Now()
// ... your job logic ...
pingWithPayload("YOUR_TOKEN", map[string]interface{}{
"duration": time.Since(start).Milliseconds(),
"status": "completed",
})
}
GitHub Actions
After a Workflow Step
steps:
- name: Run scheduled task
run: ./scripts/nightly-build.sh
- name: Ping IonHour
if: success()
run: curl -sf https://app.failsignal.com/api/signals/ping/${{ secrets.FAILSIGNAL_TOKEN }}
With Deployment Window
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Start deployment window
id: deploy-start
run: |
RESPONSE=$(curl -s -X POST https://app.failsignal.com/api/deployments \
-H "Authorization: Bearer ${{ secrets.FAILSIGNAL_API_KEY }}" \
-H "Content-Type: application/json" \
-d "{
\"projectId\": ${{ vars.PROJECT_ID }},
\"name\": \"${{ github.ref_name }}\",
\"version\": \"${{ github.sha }}\",
\"author\": \"${{ github.actor }}\",
\"autoPause\": true
}")
echo "deployment_id=$(echo $RESPONSE | jq -r '.id')" >> $GITHUB_OUTPUT
- name: Deploy
run: ./deploy.sh
- name: End deployment window
if: always()
run: |
curl -s -X PUT "https://app.failsignal.com/api/deployments/${{ steps.deploy-start.outputs.deployment_id }}/end" \
-H "Authorization: Bearer ${{ secrets.FAILSIGNAL_API_KEY }}"
Scheduled Workflow (Synthetic Monitor)
name: Heartbeat
on:
schedule:
- cron: '*/5 * * * *' # Every 5 minutes
jobs:
ping:
runs-on: ubuntu-latest
steps:
- name: Health check
run: |
STATUS=$(curl -sf -o /dev/null -w '%{http_code}' https://api.yourservice.com/health)
if [ "$STATUS" = "200" ]; then
curl -sf https://app.failsignal.com/api/signals/ping/${{ secrets.FAILSIGNAL_TOKEN }}
fi
Docker
Healthcheck
HEALTHCHECK --interval=5m --timeout=10s --retries=1 \
CMD curl -sf https://app.failsignal.com/api/signals/ping/YOUR_TOKEN || exit 1
Entrypoint Wrapper
# entrypoint.sh
#!/bin/sh
set -e
# Run the actual command
"$@"
# Ping IonHour on success
curl -sf --max-time 10 https://app.failsignal.com/api/signals/ping/$FAILSIGNAL_TOKEN > /dev/null 2>&1 || true
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["./my-job"]
Kubernetes
CronJob with Ping
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-cleanup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: cleanup
image: myapp:latest
command:
- /bin/sh
- -c
- |
./cleanup.sh && \
curl -sf https://app.failsignal.com/api/signals/ping/$FAILSIGNAL_TOKEN
env:
- name: FAILSIGNAL_TOKEN
valueFrom:
secretKeyRef:
name: failsignal
key: token
restartPolicy: OnFailure
Systemd Timer
# /etc/systemd/system/backup.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
ExecStartPost=/usr/bin/curl -sf https://app.failsignal.com/api/signals/ping/YOUR_TOKEN
# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target