Before diving into specific examples, remember these key principles:
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
All examples include timeout and retry configurations to prevent false alerts caused by temporary network issues. Always include these safeguards in production implementations.
Shell Scripts and Command Line
Basic cURL Examples
The most common way to ping heartbeat monitors from shell scripts:
GET Request
POST Request
Error Handling
# 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:
# 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!"
Heroku Scheduler
Monitor Heroku scheduled tasks:
# 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:
#!/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:
# 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
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:
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
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
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+)
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:
App Router
Pages Router
SvelteKit/Other
// 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
Basic Example
Advanced Example
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
# 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:
Basic PowerShell
Azure Functions
# 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:
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:
#!/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
Problem : Pings fail due to network timeouts or slow connections.Solution : Always configure timeouts and retries:# 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
Problem : Pings are ignored due to blocked user agents.Blocked user agents : Twitterbot, Slackbot, Googlebot, Discordbot, Facebot, TelegramBot, WhatsApp, LinkedInBotSolution : Use custom user agents:curl -A "MyApp/1.0" https://ping.checklyhq.com/your-id
Problem : Using unsupported HTTP methods.Supported : GET, POST
Not supported : PUT, DELETE, PATCHSolution : Stick to GET or POST:# Good
requests.get(heartbeat_url)
requests.post(heartbeat_url, json = metadata)
# Bad - will return error
requests.put(heartbeat_url)
Problem : Pings sent even when job fails.Solution : Structure your code correctly:# 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.