GitHub Actions
Script and command line3 steps
Failed GitHub Actions as a push to your phone
One extra step with if: failure() is enough. The key stays in a secret, and jq builds the payload.
On this page
How do I get a push notification when a GitHub Actions workflow fails?
Store the ingest key as a repository secret and add a step with if: failure() to the job. It uses jq to build JSON from the repository, workflow, branch and a link to the run, and sends it with curl to https://api.pagerdeck.com/v1/push. curl and jq come preinstalled on GitHub hosted Ubuntu runners.
- Condition
- if: failure()
- Secret
- PAGERDECK_KEY
- Tools
- curl and jq, available on ubuntu-latest
- Endpoint
- POST https://api.pagerdeck.com/v1/push
Prerequisites
- A PagerDeck account with at least one paired device, so alerts have somewhere to land.
- A source in the dashboard with its credentials, see the first step below.
- A repository with GitHub Actions. curl and jq are available on ubuntu-latest.
How to set it up
Create a source
Create a source with the slug github and copy the ingest key.
Store the secret
The command prompts for the value, so it never ends up in your shell history. Alternatively, create the secret under Settings, Secrets and variables, Actions.
bash gh secret set PAGERDECK_KEYAdd the step
Values from the github context pass through env into the script and never go directly into the run line. Anyone can choose a branch name, and there it could inject shell commands. --fail-with-body turns the step red if PagerDeck rejects the request.
yaml jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: make test - name: Notify PagerDeck if: failure() env: PAGERDECK_KEY: ${{ secrets.PAGERDECK_KEY }} TITLE: "Build failed: ${{ github.repository }}" BODY: "${{ github.workflow }} on ${{ github.ref_name }}" RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | jq -n --arg title "$TITLE" --arg body "$BODY" --arg url "$RUN_URL" \ '{title: $title, body: $body, url: $url, severity: "error", tags: ["github-actions"]}' | curl -sS --fail-with-body -X POST https://api.pagerdeck.com/v1/push \ -H "Authorization: Bearer $PAGERDECK_KEY" \ -H "Content-Type: application/json" \ --data-binary @-
Payload
PagerDeck reads these fields on POST /v1/push. Only title is required, unknown fields are ignored.
{
"title": "Disk almost full on web-01",
"body": "/ is 94% used",
"severity": "error",
"tags": [
"prod",
"disk"
],
"dedup_key": "web-01:disk",
"url": "https://status.example.com/web-01",
"ttl": "30m"
}- title
- Required, at most 250 bytes.
- body
- Body text, at most 8 KiB.
- severity
- info, warning, error or critical. Without it the source default applies.
- tags
- At most 20, 64 bytes each.
- dedup_key
- Keeps an incident together until it is resolved.
- url
- A link for the app, http and https only.
- ttl
- Delivery deadline and automatic resolve, for example 30m.
Test it
Send a test alert straight to the API after setting PAGERDECK_KEY to your key. If it shows up in the app under your source, key and source are right and any problem sits in the sender configuration.
curl -sS -X POST https://api.pagerdeck.com/v1/push \
-H "Authorization: Bearer $PAGERDECK_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"PagerDeck test","body":"If you read this, the key works.","severity":"warning"}'What the rules do with it
The sender proposes an urgency, your rule chain decides the alert level. This rule raises errors from the source github to critical and repeats until you acknowledge. Use the slug of your own source.
{
"enabled": true,
"stop": false,
"when": {
"source": [
"github"
],
"severity_at_least": "error"
},
"then": {
"level": "critical",
"repeat": {
"every": "5m",
"until": "ack",
"max": 6
}
}
}- Set the volume
- By source, title pattern, tags and time of day you can set a level from silent to critical.
- Repeat until acknowledged
- A rule repeats an alert at a fixed interval until you acknowledge it.
- Escalate
- If nobody reacts, PagerDeck notifies another address after a deadline.
What you should know
- PagerDeck only reports what the workflow sends. A workflow that never starts, for example because GitHub disabled a schedule in an inactive repository, creates no alert.
- Self hosted runners need curl, jq and outbound HTTPS to api.pagerdeck.com.
- Each failure is a separate message. If you want only one open incident per branch, set a dedup_key and close it in the successful run via /v1/resolve.
Troubleshooting
When the API answers with a status code, it usually names the cause already.
- 202
- Accepted and stored. This is not an error, delivery follows.
- 400
- The input is invalid, for example a title over 250 bytes or malformed JSON. The message field gives the reason.
- 401
- The key is missing, unknown or expired. Check the Authorization: Bearer header and that the key was copied completely.
- 402
- The account may not send. Nothing is wrong with the connection, check the plan and billing in the dashboard.
- 413
- The request is larger than 64 KiB. Limit the amount per request.
- 429
- Sent too fast or quota used up. The Retry-After header says when to continue.
Frequently asked questions
- Why not use GitHub's email?
- It arrives, but in your inbox and without rules. With PagerDeck you decide whether a red build on main wakes you at night while one on a feature branch lands quietly in the history.
- Can I report only failures on main?
- Yes. Put if: failure() && github.ref == 'refs/heads/main' on the step. Or report everything and control the volume in PagerDeck with a pattern on the text.
- Does the key show up in the log?
- No. GitHub masks secrets in the output, and the key is only in the header, never in the URL.
See red builds before anyone asks
One secret, one step. 14 days, no payment method needed.
Last reviewed: October 2026. PagerDeck, a product of Gonzi Tech UG (haftungsbeschränkt)
Product and company names mentioned are trademarks of their respective owners. PagerDeck is not affiliated with, sponsored or endorsed by them. Details in the imprint.