GitHub Actions integration
Page the on-call responder when a GitHub Actions workflow fails. Add a step to your workflow that POSTs a small JSON body to a Unit Oncall Webhook URL on failure. Unit Oncall normalizes it, routes it to a Schedule, and notifies on-call.
Setup
-
In Unit Oncall, create a Webhook (Integrations → Webhooks → New). Choose a Team and default Schedule. Either auth mode works because a workflow step can send both a body and headers. Standard mode keeps the URL self-contained; Secure mode lets you store the secret separately. Copy the generated Webhook URL.
-
Store the Webhook URL (and, for Secure mode, the secret) as repository or organization secrets — for example
UNIT_ONCALL_WEBHOOK_URLandUNIT_ONCALL_WEBHOOK_SECRET. -
Add a failure-triggered step to your workflow:
- name: Notify Unit Oncall on failure if: failure() env: UNIT_ONCALL_WEBHOOK_URL: ${{ secrets.UNIT_ONCALL_WEBHOOK_URL }} RUN_ID: ${{ github.run_id }} WORKFLOW: ${{ github.workflow }} REPOSITORY: ${{ github.repository }} REF: ${{ github.ref_name }} ACTOR: ${{ github.actor }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | # Build the JSON with jq (preinstalled on GitHub-hosted runners) so values # containing quotes or newlines can't break it. Context values are passed via # env, not interpolated into the body, to avoid shell / JSON injection. jq -n \ --arg run_id "$RUN_ID" \ --arg workflow "$WORKFLOW" \ --arg repository "$REPOSITORY" \ --arg ref "$REF" \ --arg actor "$ACTOR" \ --arg run_url "$RUN_URL" \ '{run_id: $run_id, workflow: $workflow, repository: $repository, ref: $ref, actor: $actor, run_url: $run_url}' \ | curl --fail --silent --show-error -X POST "$UNIT_ONCALL_WEBHOOK_URL" \ -H "Content-Type: application/json" -d @-For Secure mode, also pass the secret via
env(e.g.WEBHOOK_SECRET: ${{ secrets.UNIT_ONCALL_WEBHOOK_SECRET }}) and add-H "X-Webhook-Secret: $WEBHOOK_SECRET"to thecurlcommand.
Webhook URL
Unit Oncall exposes a single unified receive endpoint. The token in the path identifies the webhook (and, in Standard mode, carries the secret):
POST https://<your-api-host>/api/v1/wh/{token}
{token}is an opaque, encrypted value generated when you create the Webhook. Store it in a GitHub secret, not in plaintext in the workflow file.- Standard auth mode: the secret is embedded in the token, so the URL is self-contained.
- Secure auth mode: send the secret as the
X-Webhook-Secretheader (shown above) or awebhook_secretbody field.
Recommended transformation rule
Because the payload above is your own custom shape, the rule maps your chosen fields directly. It uses run_id as the fingerprint (each failed run is a distinct alert), builds a title from the workflow and repository, and sets a static critical severity since the step only fires on failure.
{
"name": "GitHub Actions failures",
"description": "Maps a custom GitHub Actions failure payload to Unit Oncall alerts.",
"is_active": true,
"mappings": {
"fingerprint": {
"type": "jsonpath",
"path": "$.run_id"
},
"title": {
"type": "format",
"format": "CI failed: {$.workflow} ({$.repository})"
},
"severity": {
"type": "static",
"value": "critical"
},
"message": {
"type": "format",
"format": "Workflow {$.workflow} on {$.ref} failed. Triggered by {$.actor}. Details: {$.run_url}"
},
"tags": {
"type": "jsonpath",
"path": "$.repository"
}
}
}
See the Transformation Rule reference for the full field and mapping syntax.
Troubleshooting
- No alert arrives. Confirm the step actually ran —
if: failure()only fires when an earlier step failed. Check the step logs for thecurlexit code;--failmakescurlreturn non-zero on a 4xx/5xx so the workflow surfaces delivery errors. 403 webhook disabled. The Webhook'sis_enabledisfalse. Enable it.400 bad request. The token is malformed (often a truncated secret) or, in Secure mode, theX-Webhook-Secretis missing or wrong.- Every run reuses one alert.
run_idis unique per run, so each failure is a separate alert. If you instead want to group by workflow, pointfingerprintat$.workflow(note: a later success will not auto-resolve it, since this payload has no resolved signal). - Want success/recovery signals too? This integration only fires on failure. To resolve alerts automatically you would also send a "resolved" notification on success with the same
fingerprint.
