---
title: GitHub Actions
description: Fire a heartbeat from any scheduled or post-deploy GitHub Actions job with one final step.
section: integrations
order: 4
---

# GitHub Actions

Any workflow can fire a heartbeat as its last step. One curl line per workflow.

## Setup

1. Add a repo secret `AGENTPING_PING_TOKEN`. Paste the per-agent ping token from the AgentPing settings page.
2. Add a final step that calls curl. Use `if: always()` so it runs on success or failure, and `${{ job.status }}` for the right status.

## Recipe

```yaml
name: Nightly rollup

on:
  schedule:
    - cron: '0 3 * * *'
  workflow_dispatch:

jobs:
  rollup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run the rollup
        id: rollup
        run: |
          START=$SECONDS
          ./scripts/nightly-rollup.sh
          echo "duration_ms=$(( (SECONDS - START) * 1000 ))" >> "$GITHUB_OUTPUT"

      - name: Heartbeat to AgentPing
        if: always()
        env:
          AGENTPING_PING_TOKEN: ${{ secrets.AGENTPING_PING_TOKEN }}
          DURATION_MS: ${{ steps.rollup.outputs.duration_ms }}
        run: |
          STATUS=ok
          if [ "${{ job.status }}" != "success" ]; then STATUS=fail; fi
          curl -fsS -H "X-AgentPing-Key: $AGENTPING_PING_TOKEN" \
            "https://eu.ingest.agentping.io/v1/ping?agent=nightly-rollup&status=$STATUS&duration_ms=${DURATION_MS:-0}"
```

Credentials go in `X-AgentPing-Key` so they don't appear in logs. `curl -fsS` returns non-zero on HTTP errors and prints the response body, so misconfigured tokens surface as red steps.

## Scheduled workflows

GitHub Actions schedules are best-effort; runs can be delayed during heavy load, and GitHub skips schedules for repos idle 60 days. AgentPing's schedule monitoring is the safety net. Set the same cron on the agent in the dashboard; [Pulse](/docs/pulse) pages you on missed windows.

## Reusing the step

If multiple workflows fire the same heartbeat, extract it into a composite action at `.github/actions/heartbeat/action.yml`:

```yaml
- uses: ./.github/actions/heartbeat
  if: always()
  with:
    agent: nightly-rollup
    status: ${{ job.status }}
```

:::warning
Use `secrets.AGENTPING_PING_TOKEN`, not a hardcoded literal. Never put an `apk_` API key in workflow YAML. See [Authentication](/docs/api/auth).
:::
