How to Monitor GitHub Actions with Webhooks and Cron Pings
GitHub Actions is reliable. Until it is not. A workflow that ran perfectly for six months suddenly fails silently — a dependency changed, a secret expired, a runner ran out of disk space. If your only
GitHub Actions is reliable. Until it is not. A workflow that ran perfectly for six months suddenly fails silently — a dependency changed, a secret expired, a runner ran out of disk space. If your only monitoring is "check the Actions tab when something seems wrong," you are already too late.
Here is how to build proper monitoring for GitHub Actions using two patterns: dead man's switches and webhook alerts.
Pattern 1: Dead Man's Switch
A dead man's switch is a timer that expects to be reset periodically. If it is not reset, it fires an alert. This is the simplest and most reliable monitoring pattern for scheduled workflows.
The concept: create a monitor that expects a ping every N minutes. Add a ping to the end of your GitHub Actions workflow. If the workflow completes successfully, the monitor resets. If it fails, the monitor times out and alerts you.
# .github/workflows/deploy.yml
name: Deploy
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./deploy.sh
# Ping the monitor ONLY on success
- name: Report success
if: success()
run: curl -fsS https://cronping.anethoth.com/ping/YOUR_PING_TOKENThe key detail: the ping step uses if: success(). It only runs when all previous steps succeed. If any step fails, the ping never fires, and your monitor alerts you after the expected interval passes.
Pattern 2: Start and Finish Pings
Dead man's switches tell you when a workflow fails to complete. But what if you also want to know when a workflow is taking too long? Use start and finish pings.
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Signal start
run: curl -fsS https://cronping.anethoth.com/ping/YOUR_TOKEN/start
- uses: actions/checkout@v4
- run: npm ci && npm run build && npm test
- name: Signal finish
if: always()
run: |
if [ "${{ job.status }}" = "success" ]; then
curl -fsS https://cronping.anethoth.com/ping/YOUR_TOKEN
else
curl -fsS https://cronping.anethoth.com/ping/YOUR_TOKEN/fail
fiThis gives you three data points: the workflow started, how long it took, and whether it succeeded or failed. You can set alerts for both "did not finish" and "took longer than expected."
Pattern 3: Webhook Forwarding
For event-driven workflows (pull requests, pushes, releases), dead man's switches do not work because there is no expected schedule. Instead, use GitHub's webhook events to monitor workflow outcomes.
GitHub can send webhook events for workflow runs. Configure a webhook in your repository settings pointing to a WebhookVault endpoint, then inspect the payloads to detect failures.
What to Monitor
- Scheduled workflows: Dead man's switch. Alert on missed ping.
- Deploy workflows: Start/finish pings. Alert on failure or excessive duration.
- PR check workflows: Webhook events. Track failure rate trends.
- Security scan workflows: Dead man's switch. These must never silently stop running.
Common Mistakes
Pinging on every step instead of the final step. You want to know if the entire workflow succeeded, not if individual steps ran. Put the ping at the end.
Not handling the if: always() case. If a step fails, subsequent steps do not run by default. Use if: always() for cleanup steps and if: success() for success-only pings.
Hardcoding tokens in workflow files. Use GitHub Secrets. Store your ping URL as a repository secret and reference it with ${{ secrets.CRONPING_URL }}.
No alerts on the monitor itself. A monitor that nobody checks is not monitoring. Configure webhook alerts to Slack, email, or PagerDuty so failures reach humans without humans having to remember to check.
The goal is simple: every automated process should have automated monitoring. If it is important enough to automate, it is important enough to watch.