- Ping only on success - Only send pings when your task completes successfully
- Include timeouts - Prevent hanging requests that could block your job
- Add retries - Handle temporary network issues gracefully
- Position correctly - Place pings at the very end of your success path
- Use source headers - Help identify which system sent the ping
Shell Scripts and Command Line
Basic cURL Examples
The most common way to ping heartbeat monitors from shell scripts:Copy
Ask AI
# Basic GET request with timeout and retry
curl -m 5 --retry 3 https://ping.checklyhq.com/your-heartbeat-id
# With source identification
curl -m 5 --retry 3 \
-H "Origin: backup-server-prod" \
https://ping.checklyhq.com/your-heartbeat-id
# Silent operation (no output)
curl -m 5 --retry 3 -s \
https://ping.checklyhq.com/your-heartbeat-id
wget Alternative
If cURL isn’t available, use wget instead:Copy
Ask AI
# Basic wget with timeout and retry
wget -T 5 -t 3 -q -O /dev/null \
https://ping.checklyhq.com/your-heartbeat-id
# In a complete backup script
#!/bin/bash
set -e # Exit on any error
echo "Starting database backup..."
pg_dump production_db > /tmp/backup.sql
echo "Uploading to S3..."
aws s3 cp /tmp/backup.sql s3://backups/$(date +%Y%m%d).sql
echo "Cleaning up..."
rm /tmp/backup.sql
echo "Sending success ping..."
wget -T 5 -t 3 -q -O /dev/null \
https://ping.checklyhq.com/your-heartbeat-id
echo "Backup completed successfully!"
Platform-Specific Integrations
Heroku Scheduler
Monitor Heroku scheduled tasks:Copy
Ask AI
# In your Heroku Scheduler command
run_task.sh && curl -m 5 --retry 3 https://ping.checklyhq.com/your-heartbeat-id
Render Cron Jobs
For Render cron job services:Copy
Ask AI
#!/bin/bash
# Render cron job script
# Run your task
python process_data.py
# Only ping if successful (exit code 0)
if [ $? -eq 0 ]; then
curl -m 5 --retry 3 https://ping.checklyhq.com/your-heartbeat-id
fi
Railway Cron Jobs
Similar pattern for Railway scheduled deployments:Copy
Ask AI
# In your railway cron script
npm run data-sync && \
curl -m 5 --retry 3 https://ping.checklyhq.com/your-heartbeat-id
Kubernetes Cron Jobs
Basic CronJob with Heartbeat
Copy
Ask AI
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-backup
namespace: production
spec:
schedule: "0 2 * * *" # 2 AM daily
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
containers:
- name: backup-job
image: your-registry/backup-job:latest
command:
- /bin/sh
- -c
args:
- |
echo "Running backup job..."
# Run your actual backup logic
/scripts/run-backup.sh
# Only ping if backup succeeded
if [ $? -eq 0 ]; then
echo "Backup successful, sending heartbeat..."
curl -m 5 --retry 3 \
-H "Origin: k8s-cluster-prod" \
https://ping.checklyhq.com/your-heartbeat-id
else
echo "Backup failed, no heartbeat sent"
exit 1
fi
env:
- name: HEARTBEAT_URL
valueFrom:
secretKeyRef:
name: heartbeat-secrets
key: backup-heartbeat-url
restartPolicy: OnFailure
backoffLimit: 2
Advanced CronJob with Sidecar
For more complex scenarios, use a sidecar pattern:Copy
Ask AI
apiVersion: batch/v1
kind: CronJob
metadata:
name: data-processing
spec:
schedule: "0 */6 * * *" # Every 6 hours
jobTemplate:
spec:
template:
spec:
containers:
# Main job container
- name: processor
image: your-app/data-processor:latest
command: ["/app/process-data"]
volumeMounts:
- name: shared-status
mountPath: /shared
# Heartbeat sidecar
- name: heartbeat
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
# Wait for main container to finish
while [ ! -f /shared/status ]; do
sleep 5
done
# Check if job succeeded
if [ "$(cat /shared/status)" = "success" ]; then
curl -m 5 --retry 3 \
https://ping.checklyhq.com/your-heartbeat-id
fi
volumeMounts:
- name: shared-status
mountPath: /shared
volumes:
- name: shared-status
emptyDir: {}
restartPolicy: OnFailure
Node.js and JavaScript
Built-in HTTPS Module
Copy
Ask AI
import https from 'https';
async function pingHeartbeat(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const options = {
timeout: 5000,
};
const req = https.get(url, options, (res) => {
console.log(`Heartbeat ping status: ${res.statusCode}`);
resolve();
});
req.on('error', (error) => {
console.error(`Heartbeat ping failed: ${error.message}`);
reject(error);
});
req.on('timeout', () => {
req.destroy();
reject(new Error('Heartbeat ping timeout'));
});
});
}
// Usage in your job
async function runScheduledJob() {
try {
// Your job logic here
await processData();
await generateReports();
// Only ping on success
await pingHeartbeat('https://ping.checklyhq.com/your-heartbeat-id');
console.log('Job completed successfully');
} catch (error) {
console.error('Job failed:', error);
// Don't ping on failure - let heartbeat monitor alert
process.exit(1);
}
}
Using Axios
Copy
Ask AI
import axios from 'axios';
const HEARTBEAT_URL = 'https://ping.checklyhq.com/your-heartbeat-id';
async function pingHeartbeat(source?: string): Promise<void> {
try {
const headers: Record<string, string> = {};
if (source) {
headers['Origin'] = source;
}
const response = await axios.get(HEARTBEAT_URL, {
timeout: 5000,
headers,
// Axios retry configuration
validateStatus: (status) => status < 500, // Don't throw on 4xx
});
console.log(`Heartbeat sent successfully: ${response.status}`);
} catch (error) {
console.error('Failed to send heartbeat:', error.message);
throw error;
}
}
// In your scheduled job
async function scheduledTask() {
try {
console.log('Starting scheduled task...');
// Your job logic
await syncDatabase();
await updateCache();
await sendReports();
// Send success ping
await pingHeartbeat('newsletter-service');
console.log('Scheduled task completed successfully');
} catch (error) {
console.error('Scheduled task failed:', error);
// Exit with error code - don't send heartbeat
process.exit(1);
}
}
Fetch API (Modern Browsers/Node.js 18+)
Copy
Ask AI
async function pingHeartbeat(url, source = null) {
const headers = {
'Content-Type': 'application/json'
};
if (source) {
headers['Origin'] = source;
}
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, {
method: 'GET',
headers,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`Heartbeat failed: ${response.status}`);
}
console.log('Heartbeat sent successfully');
} catch (error) {
if (error.name === 'AbortError') {
console.error('Heartbeat request timed out');
} else {
console.error('Heartbeat request failed:', error);
}
throw error;
}
}
Vercel Cron Jobs
Monitor your Vercel cron jobs across different routing patterns:Copy
Ask AI
// app/api/cron/newsletter/route.js
export async function GET(request) {
try {
console.log('Starting newsletter job...');
// Your cron job logic
const subscribers = await getSubscribers();
await sendNewsletter(subscribers);
await updateMetrics();
// Send success heartbeat
const heartbeatUrl = 'https://ping.checklyhq.com/your-heartbeat-id';
const response = await fetch(heartbeatUrl, {
method: 'GET',
headers: {
'Origin': 'vercel-newsletter'
}
});
console.log(`Heartbeat sent: ${response.status}`);
return new Response(JSON.stringify({
success: true,
processed: subscribers.length
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Newsletter job failed:', error);
// Don't send heartbeat on failure
return new Response(JSON.stringify({
success: false,
error: error.message
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
Python Examples
Using Requests Library
Copy
Ask AI
import requests
import sys
import time
def ping_heartbeat(url, source=None, timeout=5, retries=3):
"""Send heartbeat ping with retry logic."""
headers = {}
if source:
headers['Origin'] = source
for attempt in range(retries):
try:
response = requests.get(url, headers=headers, timeout=timeout)
response.raise_for_status()
print(f"Heartbeat sent successfully: {response.status_code}")
return True
except requests.exceptions.RequestException as e:
print(f"Heartbeat attempt {attempt + 1} failed: {e}")
if attempt < retries - 1:
time.sleep(1) # Wait before retry
print("All heartbeat attempts failed")
return False
def run_scheduled_job():
"""Example scheduled job with heartbeat monitoring."""
heartbeat_url = "https://ping.checklyhq.com/your-heartbeat-id"
try:
print("Starting data processing job...")
# Your job logic here
process_data()
generate_reports()
cleanup_temp_files()
print("Job completed successfully")
# Send success heartbeat
if not ping_heartbeat(heartbeat_url, source="data-processor"):
print("Warning: Failed to send heartbeat ping")
except Exception as e:
print(f"Job failed: {e}")
# Don't send heartbeat on failure
sys.exit(1)
if __name__ == "__main__":
run_scheduled_job()
Django Management Commands
Copy
Ask AI
# management/commands/send_newsletter.py
from django.core.management.base import BaseCommand
from django.conf import settings
import requests
class Command(BaseCommand):
help = 'Send weekly newsletter'
def handle(self, *args, **options):
try:
self.stdout.write('Starting newsletter job...')
# Newsletter logic
subscribers = self.get_subscribers()
sent_count = self.send_newsletters(subscribers)
self.stdout.write(f'Newsletter sent to {sent_count} subscribers')
# Send heartbeat
heartbeat_url = settings.NEWSLETTER_HEARTBEAT_URL
response = requests.get(heartbeat_url, timeout=5)
response.raise_for_status()
self.stdout.write(
self.style.SUCCESS('Newsletter job completed successfully')
)
except Exception as e:
self.stdout.write(
self.style.ERROR(f'Newsletter job failed: {e}')
)
raise
PowerShell Examples
For Windows environments and Azure functions:Copy
Ask AI
# Basic heartbeat ping with error handling
function Send-HeartbeatPing {
param(
[string]$Url,
[string]$Source = $null,
[int]$TimeoutSec = 5,
[int]$MaxRetries = 3
)
$headers = @{}
if ($Source) {
$headers["Origin"] = $Source
}
for ($i = 0; $i -lt $MaxRetries; $i++) {
try {
$response = Invoke-RestMethod -Uri $Url -Headers $headers -TimeoutSec $TimeoutSec -MaximumRetryCount 0
Write-Host "Heartbeat sent successfully"
return $true
}
catch {
Write-Warning "Heartbeat attempt $($i + 1) failed: $($_.Exception.Message)"
if ($i -lt ($MaxRetries - 1)) {
Start-Sleep -Seconds (2 * ($i + 1))
}
}
}
Write-Error "All heartbeat attempts failed"
return $false
}
# Example backup script
try {
Write-Host "Starting backup process..."
# Your backup logic
& "C:\Scripts\DatabaseBackup.exe"
if ($LASTEXITCODE -ne 0) {
throw "Database backup failed with exit code $LASTEXITCODE"
}
& "C:\Scripts\UploadToCloud.exe"
if ($LASTEXITCODE -ne 0) {
throw "Cloud upload failed with exit code $LASTEXITCODE"
}
Write-Host "Backup completed successfully"
# Send heartbeat
$heartbeatUrl = "https://ping.checklyhq.com/your-heartbeat-id"
Send-HeartbeatPing -Url $heartbeatUrl -Source "windows-backup-server"
} catch {
Write-Error "Backup process failed: $($_.Exception.Message)"
exit 1
}
Advanced Patterns
Conditional Heartbeats
Sometimes you only want to ping under certain conditions:Copy
Ask AI
def conditional_heartbeat_example():
"""Only ping heartbeat if certain conditions are met."""
# Run your job
results = process_data()
# Only ping if we processed a significant amount of data
if results['processed_count'] > 100:
ping_heartbeat(
url="https://ping.checklyhq.com/your-heartbeat-id",
metadata={'processed': results['processed_count']}
)
print(f"Heartbeat sent - processed {results['processed_count']} items")
else:
print(f"Skipped heartbeat - only processed {results['processed_count']} items")
Multi-Step Job Monitoring
For complex jobs with multiple phases:Copy
Ask AI
#!/bin/bash
set -e
HEARTBEAT_URL="https://ping.checklyhq.com/your-heartbeat-id"
JOB_FAILED=false
# Function to send heartbeat on success
send_success_heartbeat() {
if [ "$JOB_FAILED" = false ]; then
curl -m 5 --retry 3 -H "Origin: multi-step-job" "$HEARTBEAT_URL"
echo "Success heartbeat sent"
fi
}
# Trap to ensure heartbeat is sent on successful completion
trap send_success_heartbeat EXIT
# Step 1: Download data
echo "Step 1: Downloading data..."
if ! download_data.sh; then
echo "Data download failed"
JOB_FAILED=true
exit 1
fi
# Step 2: Process data
echo "Step 2: Processing data..."
if ! process_data.sh; then
echo "Data processing failed"
JOB_FAILED=true
exit 1
fi
# Step 3: Upload results
echo "Step 3: Uploading results..."
if ! upload_results.sh; then
echo "Results upload failed"
JOB_FAILED=true
exit 1
fi
echo "All steps completed successfully"
# Heartbeat will be sent by the EXIT trap
Troubleshooting
Common Issues
Network Timeouts
Network Timeouts
Problem: Pings fail due to network timeouts or slow connections.Solution: Always configure timeouts and retries:
Copy
Ask AI
# Good: With timeout and retry
curl -m 5 --retry 3 --retry-delay 2 https://ping.checklyhq.com/your-id
# Add connection timeout too
curl --connect-timeout 10 -m 30 --retry 3 https://ping.checklyhq.com/your-id
Blocked User Agents
Blocked User Agents
Problem: Pings are ignored due to blocked user agents.Blocked user agents: Twitterbot, Slackbot, Googlebot, Discordbot, Facebot, TelegramBot, WhatsApp, LinkedInBotSolution: Use custom user agents:
Copy
Ask AI
curl -A "MyApp/1.0" https://ping.checklyhq.com/your-id
Wrong HTTP Methods
Wrong HTTP Methods
Problem: Using unsupported HTTP methods.Supported: GET, POST
Not supported: PUT, DELETE, PATCHSolution: Stick to GET or POST:
Not supported: PUT, DELETE, PATCHSolution: Stick to GET or POST:
Copy
Ask AI
# Good
requests.get(heartbeat_url)
requests.post(heartbeat_url, json=metadata)
# Bad - will return error
requests.put(heartbeat_url)
Timing in Error Handling
Timing in Error Handling
Problem: Pings sent even when job fails.Solution: Structure your code correctly:
Copy
Ask AI
# Good: Ping only on success
try:
run_job()
ping_heartbeat() # Only reached if run_job() succeeds
except Exception:
# Don't ping on failure
logging.error("Job failed")
# Bad: Always pings
try:
run_job()
except Exception:
logging.error("Job failed")
finally:
ping_heartbeat() # Always runs!
Testing Your Implementation
Before deploying, test your heartbeat integration:- Manual ping test: Use the Checkly UI to send manual pings
- Timeout test: Temporarily block network access to verify timeout behavior
- Failure test: Force your job to fail and confirm no ping is sent
- Retry test: Add temporary network delays to test retry logic
Start with a short grace period (like 5 minutes) while testing, then increase it to your production requirements once you’re confident in the timing.