GitHub Actions integration

Monitor scheduled GitHub Actions workflows with Drumbeats. Reusable workflow, ping wiring, and patterns for matrix and concurrent runs.

This guide shows how to wire Drumbeats into GitHub Actions workflows. By the end your scheduled workflows send start / success / failure pings automatically — no per-step copy-paste, no flaky steps that mask failures.

The patterns assume scheduled workflows (schedule: triggers). The same approach works for workflow_dispatch and repository_dispatch triggers; only the trigger metadata changes.

Quick example

The simplest pattern adds three curl steps to a workflow job:

.github/workflows/nightly-sync.yml
name: Nightly sync
on:
  schedule:
    - cron: "0 2 * * *"

jobs:
  sync:
    runs-on: ubuntu-latest
    env:
      DRUMBEATS_MONITOR: 11111111-2222-3333-4444-555555555555
      RUN_ID: "gh-${{ github.run_id }}"
    steps:
      - uses: actions/checkout@v4

      - name: Drumbeats — start
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/$DRUMBEATS_MONITOR/start?run_id=$RUN_ID"

      - name: Run sync
        id: sync
        run: ./scripts/sync.sh

      - name: Drumbeats — success
        if: success()
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/$DRUMBEATS_MONITOR/success?run_id=$RUN_ID"

      - name: Drumbeats — failure
        if: failure()
        run: |
          curl -sf --max-time 3 -X POST \
            "https://api.drumbeats.io/v1/ping/$DRUMBEATS_MONITOR/failure?run_id=$RUN_ID" \
            -H 'Content-Type: application/json' \
            -d "$(jq -n --arg p "Workflow ${{ github.workflow }} failed at step ${{ github.action }}" '{payload: $p}')"
name: Nightly sync
on:
  schedule:
    - cron: "0 2 * * *"

jobs:
  sync:
    runs-on: ubuntu-latest
    env:
      DRUMBEATS_MONITOR: 11111111-2222-3333-4444-555555555555
      RUN_ID: "gh-${{ github.run_id }}"
    steps:
      - uses: actions/checkout@v4

      - name: Drumbeats — start
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/$DRUMBEATS_MONITOR/start?run_id=$RUN_ID"

      - name: Run sync
        id: sync
        run: ./scripts/sync.sh

      - name: Drumbeats — success
        if: success()
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/$DRUMBEATS_MONITOR/success?run_id=$RUN_ID"

      - name: Drumbeats — failure
        if: failure()
        run: |
          curl -sf --max-time 3 -X POST \
            "https://api.drumbeats.io/v1/ping/$DRUMBEATS_MONITOR/failure?run_id=$RUN_ID" \
            -H 'Content-Type: application/json' \
            -d "$(jq -n --arg p "Workflow ${{ github.workflow }} failed at step ${{ github.action }}" '{payload: $p}')"

if: success() / if: failure() ensure exactly one of the two final pings fires. github.run_id makes a stable run_id so the Drumbeats record links back to the workflow run.

Reusable workflow

For more than a couple of monitored workflows, hoist the pattern into a reusable workflow so each consumer is one line of YAML:

.github/workflows/with-drumbeats.yml
name: With Drumbeats
on:
  workflow_call:
    inputs:
      monitor-id:
        required: true
        type: string
      command:
        required: true
        type: string

jobs:
  run:
    runs-on: ubuntu-latest
    env:
      RUN_ID: "gh-${{ github.run_id }}-${{ github.run_attempt }}"
    steps:
      - uses: actions/checkout@v4

      - name: Drumbeats — start
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/${{ inputs.monitor-id }}/start?run_id=$RUN_ID"

      - name: Run command
        id: run
        run: ${{ inputs.command }}

      - name: Drumbeats — success
        if: success()
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/${{ inputs.monitor-id }}/success?run_id=$RUN_ID"

      - name: Drumbeats — failure
        if: failure()
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/${{ inputs.monitor-id }}/failure?run_id=$RUN_ID"
name: With Drumbeats
on:
  workflow_call:
    inputs:
      monitor-id:
        required: true
        type: string
      command:
        required: true
        type: string

jobs:
  run:
    runs-on: ubuntu-latest
    env:
      RUN_ID: "gh-${{ github.run_id }}-${{ github.run_attempt }}"
    steps:
      - uses: actions/checkout@v4

      - name: Drumbeats — start
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/${{ inputs.monitor-id }}/start?run_id=$RUN_ID"

      - name: Run command
        id: run
        run: ${{ inputs.command }}

      - name: Drumbeats — success
        if: success()
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/${{ inputs.monitor-id }}/success?run_id=$RUN_ID"

      - name: Drumbeats — failure
        if: failure()
        run: curl -sf --max-time 3 "https://api.drumbeats.io/v1/ping/${{ inputs.monitor-id }}/failure?run_id=$RUN_ID"

Consumer:

.github/workflows/nightly-sync.yml
name: Nightly sync
on:
  schedule:
    - cron: "0 2 * * *"

jobs:
  sync:
    uses: ./.github/workflows/with-drumbeats.yml
    with:
      monitor-id: 11111111-2222-3333-4444-555555555555
      command: ./scripts/sync.sh
name: Nightly sync
on:
  schedule:
    - cron: "0 2 * * *"

jobs:
  sync:
    uses: ./.github/workflows/with-drumbeats.yml
    with:
      monitor-id: 11111111-2222-3333-4444-555555555555
      command: ./scripts/sync.sh

Error handling

  • Cancelled runs. A workflow cancelled by a user (or by concurrency: cancellation) reports neither success nor failure — neither if: success() nor if: failure() fires. Drumbeats catches the hang via max_duration_seconds on the monitor.
  • Matrix jobs. Use a stable run_id per matrix cell: "gh-${{ github.run_id }}-${{ matrix.target }}". Otherwise the matrix sends overlapping pings to the same monitor and Drumbeats can't correlate them.
  • Composite actions. Wrap the actions/checkout step in the reusable workflow above and the consumer never sees it. Composite actions can also publish the ping logic — fine for repositories that vendor their own actions.

Production patterns

  • Use concurrency: to prevent overlapping runs. A nightly job that occasionally takes 90 minutes can collide with the next run. concurrency: { group: nightly-sync, cancel-in-progress: false } queues the second run instead.
  • Surface the failing step in the payload. Use ${{ github.action }} and ${{ steps.run.outputs.* }} in the failure ping payload so the incident timeline points at the exact step.
  • GitHub Actions free minutes. Pings consume a beat each but no GitHub Actions minutes worth measuring — the curl step is sub-second.
  • Multi-environment via slug. Push staging and prod through the same workflow with vars.DRUMBEATS_PROJECT_ID and vars.DRUMBEATS_SLUG, calling /v1/s-ping/<project>/<slug>/<event> instead of the monitor-ID endpoint. See Ping API: scheduled pings.
  • Monitor types — pick Cron for scheduled workflows; Event-driven for workflow_dispatch triggers.
  • Ping API — endpoint and parameter reference.
  • Production hardening — retries, timeouts, observability patterns.
  • Alternatives — how Drumbeats handles GitHub Actions monitoring vs Cronitor and Healthchecks.io.