GitHub webhooks: get pushes, releases and red Actions on your phone

July 20, 2026 · updated October 2026 · 6 min read

There are two different mechanisms in GitHub that both get called "webhooks", and picking the wrong one is why most setups end up more complicated than they need to be. Here's the distinction, the events worth subscribing to, and how to get any of them onto your phone with a sound you'll recognise.

Repository webhooks vs. GitHub Actions

This trips people up constantly, so it's worth 60 seconds:

  • A repository webhook is configured in Settings → Webhooks on the repo (or org). GitHub POSTs a JSON payload to your URL whenever a subscribed event happens. Nothing runs in CI, nothing costs minutes, and it fires for things Actions can't easily see – a new star, an issue comment, a branch deletion.
  • A GitHub Actions step runs inside a workflow. It only fires when a workflow runs, it costs Actions minutes, but it has the full job context: which job failed, at which step, on which commit.

The rule of thumb: if you want to know something happened in the repo, use a repository webhook. If you want to know something happened in your pipeline, add a step to the workflow. Many people reach for Actions because it's familiar and then wonder why they can't get notified about a new issue.

Route 1: a repository webhook straight to your phone

A Webhooky endpoint is a URL that accepts any POST and turns it into a push notification. GitHub needs a payload URL. That's the whole integration – no server, no Actions minutes:

  1. In the Webhooky app, create an endpoint. Name it after the repo, pick a sound, copy the URL.
  2. In the repository, open Settings → Webhooks → Add webhook.
  3. Paste the URL as the Payload URL, set Content type to application/json, and choose which events to send.

GitHub's payloads are large and event-specific. Webhooky doesn't parse them: since the body has no title or message field, it falls back to the title, text and sound you configured on the endpoint. You get "something happened in repo X" instantly, with its own sound.

The events most worth subscribing to:

  • push – commits landed on a branch
  • pull_request – opened, closed, merged, review requested
  • issues – someone opened or closed an issue
  • release – a release was published
  • workflow_run – a workflow finished, including its conclusion
  • deployment – a deployment was created

Every delivery carries headers worth knowing about: X-GitHub-Event tells you which event it was, X-GitHub-Delivery is a unique ID for that delivery, and X-Hub-Signature-256 carries an HMAC-SHA256 hex digest of the body keyed with your secret – but only if you set a secret. (X-Hub-Signature is the SHA-1 version, kept for compatibility; use the 256 one.) Webhooky doesn't verify that signature, so anyone who learns your endpoint URL could trigger a notification. For a build alert that's a nuisance rather than a risk; if it matters, use route 3.

Route 2: one step in GitHub Actions

When you want the failure details, not just the fact of a failure, add this as the last step of any job. The if: failure() condition means it only runs when something before it failed:

- name: Notify on failure
  if: failure()
  run: |
    curl -X POST "https://api.webhooky.app/${{ secrets.WEBHOOKY_KEY }}" \
      -H "Content-Type: application/json" \
      -d "{\"title\":\"❌ CI failed\",
           \"message\":\"${{ github.repository }} · ${{ github.workflow }} · ${{ github.ref_name }}\",
           \"sound\":\"buzzer\"}"

Put the endpoint key in Settings → Secrets and variables → Actions rather than in the workflow file, so it doesn't leak through a fork or a public repo.

Two variations worth having. Use if: success() with a quieter sound on your deploy job, so a successful production deploy gets its own confirmation. And use if: always() on a nightly job, because the thing you actually want to know about a scheduled workflow is when it silently stopped running at all.

Route 3: a small handler, if you want the details and the signature check

If you want "PR #412 merged by Anna" rather than "something happened", you need to parse the payload – and once you're doing that, verify the signature too:

import crypto from "node:crypto";

export async function onGitHubWebhook(req, res) {
  const sig = req.get("X-Hub-Signature-256") ?? "";
  const mac = "sha256=" + crypto
    .createHmac("sha256", process.env.GH_WEBHOOK_SECRET)
    .update(req.rawBody)                       // the RAW body, before JSON.parse
    .digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mac))) {
    return res.sendStatus(401);
  }
  res.sendStatus(200);

  if (req.get("X-GitHub-Event") !== "pull_request") return;
  const { action, pull_request: pr, repository } = req.body;
  if (action !== "closed" || !pr.merged) return;

  await fetch(process.env.WEBHOOKY_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      title: "🚀 PR merged",
      message: `${repository.full_name} #${pr.number} by ${pr.merged_by.login}`,
      sound: "level_up_2",
    }),
  });
}

Two details that cause most of the bugs here: compute the HMAC over the raw body, because re-serialising parsed JSON changes the bytes and the signature will never match; and use a timing-safe comparison rather than ===.

A sound per signal

This is the part that makes it genuinely useful rather than just more noise. Create one endpoint per signal and give each a sound that carries meaning – a buzzer for a failed build, something quiet and pleasant for a successful deploy, a distinct tone for a new issue. After a few days you stop reading the notification: a red pipeline sounds like a red pipeline. There are 40 sounds, selectable per endpoint or overridden per message with the sound field, and each endpoint keeps its own history.

GitLab, Jenkins and the rest

The same two-route pattern applies everywhere. GitLab has project webhooks under Settings → Webhooks plus pipeline jobs – see GitLab webhooks. Jenkins can POST from a post-build step, covered in Jenkins build notifications. For deploy platforms there's Vercel and Netlify.

Get Webhooky

100 free notifications every month, no account, endpoint ready in two minutes.

Get it on Google Play Download on the App Store

Frequently asked questions

What is the difference between a GitHub webhook and a GitHub Action?

A repository webhook is configured in Settings → Webhooks and POSTs a payload to your URL when a repo event happens – no CI runs and no Actions minutes are used. A GitHub Actions step runs inside a workflow, so it only fires when a workflow runs, but it has the full job context.

How do I get a notification when a GitHub Action fails?

Add a final step to the job with if: failure() that POSTs to a notification URL. Alternatively subscribe a repository webhook to the workflow_run event, which carries the workflow's conclusion.

How do I verify a GitHub webhook signature?

Set a secret on the webhook. GitHub then sends X-Hub-Signature-256, an HMAC-SHA256 hex digest of the request body keyed with that secret. Recompute it over the raw body before JSON parsing and compare with a timing-safe comparison.

Which GitHub webhook events exist?

Many, but the ones most people want are push, pull_request, issues, release, workflow_run and deployment. The event name for any delivery is in the X-GitHub-Event header.