Skip to main content
Before diving into specific examples, remember these key principles:
  1. Ping only on success - Only send pings when your task completes successfully
  2. Include timeouts - Prevent hanging requests that could block your job
  3. Add retries - Handle temporary network issues gracefully
  4. Position correctly - Place pings at the very end of your success path
  5. 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:
# 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!"

Platform-Specific Integrations

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/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

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 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, PATCH
Solution: 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:
  1. Manual ping test: Use the Checkly UI to send manual pings
  2. Timeout test: Temporarily block network access to verify timeout behavior
  3. Failure test: Force your job to fail and confirm no ping is sent
  4. 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.