Cron jobs are silent by default. They run in the background, and when they fail — whether it's a script error, a timeout, or a missing dependency — you usually find out hours later, when someone notices that the data wasn't processed or the backups didn't run.
The fix is simple: get a Slack message the moment a cron job exits with an error. Here's how to do it, from a quick shell script approach to a cleaner setup that works across all your jobs.
Option 1: Slack Webhooks (The Quick Way)
Slack lets you create an Incoming Webhook URL for a channel. You go to your Slack app settings, enable Incoming Webhooks, create one for the channel you want, and you get a URL. Then you POST to it:
#!/bin/bash SLACK_WEBHOOK="https://hooks.slack.com/services/T.../B.../..." # Run your job and capture the exit code python3 /home/deploy/scripts/process_orders.py EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then curl -s -X POST "$SLACK_WEBHOOK" \ -H "Content-Type: application/json" \ -d "{\"text\": \"Cron job failed (exit $EXIT_CODE): process_orders.py\"}" fi
This works. But as soon as you have more than one or two jobs, you'll run into the same problems that come with any direct Slack Webhook setup:
- One webhook URL per channel. If you have five cron jobs posting to different channels, you're managing five secrets in five crontabs.
- The webhook URL is the secret. It's hardcoded or stored in a shell script on your server. If it leaks or gets rotated, you update every script manually.
- Slack only. If someone on your team prefers Telegram, or you want the on-call engineer to get a phone notification at 3 AM, that's a separate integration.
- No history. There's no record of what was sent, when, or whether anyone saw it.
For a single script on a personal project, webhooks are perfectly fine. For a production server with multiple jobs, it becomes a maintenance headache.
Option 2: A Notification API (The Cleaner Way)
Instead of calling Slack directly, you send the alert to a notification API that handles delivery. With NotificationsBot, one API call can reach Slack, Telegram, Discord, or Email — whoever is subscribed to that channel. You manage subscribers in a dashboard, not in your cron scripts.
curl -s -X POST https://api.notificationsbot.com/event \ -H "Authorization: Bearer $NOTIFICATIONSBOT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "cron-alerts", "title": "Cron job failed", "message": "process_orders.py exited with code 1" }'
One API key, one endpoint, all your jobs. If you want to add a team member to the alerts, you add them in the dashboard. No script changes required.
With Slack webhooks, your cron scripts are coupled to Slack. With a notification API, your scripts just report what happened and the routing is handled separately. You can add new recipients, switch platforms, or split alerts into channels without touching a single crontab.
Setting Up Slack Alerts with NotificationsBot
If you don't have an account yet, the setup takes about 5 minutes:
- Sign up at app.notificationsbot.com — free tier, no credit card
- Create a channel (e.g.,
cron-alerts) - Add a subscriber and choose Slack as the platform
- Link the subscriber — connect their Slack account through the onboarding flow
- Assign the subscriber to the channel
- Create an API key in Settings and store it as an environment variable on your server
From here, any event sent to cron-alerts lands in
Slack. You can add more subscribers later — different people,
different platforms — without changing your scripts.
A Reusable Shell Wrapper
The cleanest pattern is a small wrapper function you source in every cron script. It runs the job and fires an alert only when it fails, including the exit code and any output.
#!/bin/bash # /usr/local/bin/notify.sh notify_on_failure() { local job_name="$1" shift local output output=$("$@" 2>&1) local exit_code=$? if [ $exit_code -ne 0 ]; then local msg="Exit code: $exit_code" if [ -n "$output" ]; then msg="$msg\n\n$output" fi curl -s -X POST https://api.notificationsbot.com/event \ -H "Authorization: Bearer $NOTIFICATIONSBOT_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"channel\": \"cron-alerts\", \"title\": \"Cron failed: $job_name\", \"message\": \"$msg\" }" fi return $exit_code }
Then your crontab becomes clean and consistent:
NOTIFICATIONSBOT_API_KEY=your_api_key_here # Every night at 2 AM — fails silently without the wrapper 0 2 * * * source /usr/local/bin/notify.sh && notify_on_failure "nightly-backup" /home/deploy/scripts/backup.sh # Every 15 minutes */15 * * * * source /usr/local/bin/notify.sh && notify_on_failure "order-sync" python3 /home/deploy/scripts/sync_orders.py # Every hour 0 * * * * source /usr/local/bin/notify.sh && notify_on_failure "report-generator" node /home/deploy/scripts/generate_reports.js
Python Cron Jobs
If your cron job is a Python script, you can add failure notifications directly in the exception handler:
import os import sys import requests import traceback API_KEY = os.environ["NOTIFICATIONSBOT_API_KEY"] def alert_failure(job: str, error: str): requests.post( "https://api.notificationsbot.com/event", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "channel": "cron-alerts", "title": f"Cron failed: {job}", "message": error, }, timeout=5, ) if __name__ == "__main__": try: main() except Exception: alert_failure("process-orders", traceback.format_exc()) sys.exit(1)
This gives you the full traceback in the Slack message, so you know exactly what failed and where — not just that the job exited with code 1.
Node.js Cron Jobs
// run with: node sync_orders.js async function main() { // your cron job logic here } async function alertFailure(job, error) { await fetch("https://api.notificationsbot.com/event", { method: "POST", headers: { "Authorization": `Bearer ${process.env.NOTIFICATIONSBOT_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ channel: "cron-alerts", title: `Cron failed: ${job}`, message: error.message || String(error), }), }); } main().catch(async (err) => { await alertFailure("sync-orders", err); process.exit(1); });
Slack Webhooks vs NotificationsBot
- Setup per job — Webhooks: one URL to manage per channel. NotificationsBot: one API key for all jobs
- Multi-platform — Webhooks: Slack only. NotificationsBot: Slack + Telegram + Discord + Email
- Recipient management — Webhooks: whoever is in the channel. NotificationsBot: manage subscribers from a dashboard
- Failure details — Both support custom messages; NotificationsBot stores a searchable event history
- Secret rotation — Webhooks: update every script on the server. NotificationsBot: rotate the API key once in settings
- Adding a new recipient — Webhooks: add them to the Slack channel. NotificationsBot: add a subscriber in the dashboard, no script changes
Only alert on failure, not on every run. A cron job that runs 96 times a day and always succeeds shouldn't generate 96 Slack messages. The wrapper function above already handles this — it stays silent on success and alerts only when something goes wrong.
Bonus: On-Call Escalation
One of the most useful setups for production is having cron failures notify both a Slack channel (for the whole team) and a Telegram subscriber (for the on-call engineer's phone). The team sees the alert in Slack during work hours, and the on-call person gets a personal Telegram ping at 2 AM when the nightly backup fails.
With NotificationsBot, this is just adding two subscribers to
the same cron-alerts channel — one Slack, one
Telegram. The cron scripts don't change at all.
Never Miss a Failed Cron Job Again
Set up a free account, add a Slack subscriber, and wire up your first cron job alert in under 10 minutes.
Start for free