How to Send a GitHub Issue-Closed Alert to a Shared Room
Trigger on one labeled issue closure, keep the issue details in GitHub, and send one fixed retry-safe alert to a shared PingRoom Room.

Use GitHub's own notifications when each person only needs updates inside GitHub. When one selected issue closure should reach a separate shared group, add an opt-in label and let a small GitHub Actions workflow send fixed copy to a PingRoom Room.
The recipe below runs on issues.closed, then stops unless the issue has a shared-status label. GitHub remains the record for the title, description, discussion, closure reason, and person who closed it. PingRoom carries only the short shared signal.
This is a webhook recipe, not a native GitHub integration. An issue closing does not prove that the work was correct, deployed, or approved.
Try GitHub's notification controls first
GitHub lets people watch a repository and subscribe to Issue updates. GitHub Mobile also has its own push settings and working hours. Use those controls when the people who care already work in GitHub and want its full context.
Add a shared PingRoom path only when the closure has a separate attention job.
| Need | Better starting point |
|---|---|
| A maintainer wants normal updates for issues they follow | GitHub notifications |
| A selected closure should reach one small shared attention channel | GitHub Actions → PingRoom incoming webhook |
| Someone needs the title, discussion, labels, or closure reason | Open the issue in GitHub |
| Closing the issue must deploy, approve, or change another system | Keep that system's own workflow authoritative |
The opt-in label matters. Without it, a busy repository can turn every closed issue into noise. Choose a label that means “send the shared status signal,” not “this issue is important forever.” This example uses shared-status.
Create the Room and protect its URL
Create or choose a private Room for the people who need the update. The Room owner needs PingRoom Pro for incoming webhooks.
Open Settings → Connections → From another app, add a webhook, leave Quick Ping on Custom message, and copy the full URL. The incoming-webhook walkthrough covers setup. The webhook reference carries the current request limits and response contract.
In the GitHub repository, open Settings → Secrets and variables → Actions and add a repository secret named PINGROOM_WEBHOOK_URL. Treat that URL as a credential. Do not put it in the workflow file, an issue, a log, an artifact, or browser-side code.
Create the shared-status label and agree who may add it. The label is a routing choice. It does not change the issue's meaning or authorize anyone to close it.
Add one issue-closed workflow
Save this as .github/workflows/issue-closed-room-alert.yml on the repository's default branch. Change REPOSITORY_LABEL to a short name that is safe on a lock screen.
name: Shared issue-closed alert on: issues: types: [closed] permissions: {} jobs: notify-room: if: contains(github.event.issue.labels.*.name, 'shared-status') runs-on: ubuntu-latest env: PINGROOM_WEBHOOK_URL: ${{ secrets.PINGROOM_WEBHOOK_URL }} REPOSITORY_LABEL: "Selected repository" REPOSITORY: ${{ github.repository }} ISSUE_ID: ${{ github.event.issue.id }} ISSUE_NUMBER: ${{ github.event.issue.number }} RUN_ID: ${{ github.run_id }} steps: - name: Send shared issue-closed alert continue-on-error: true shell: bash run: | node <<'NODE' const { createHash } = require("node:crypto"); async function main() { const webhookUrl = process.env.PINGROOM_WEBHOOK_URL; const label = process.env.REPOSITORY_LABEL; const repository = process.env.REPOSITORY; const issueId = process.env.ISSUE_ID; const issueNumber = process.env.ISSUE_NUMBER; const runId = process.env.RUN_ID; if (!webhookUrl) throw new Error("Missing PINGROOM_WEBHOOK_URL"); const destination = new URL(webhookUrl); if (destination.protocol !== "https:") throw new Error("PINGROOM_WEBHOOK_URL must use HTTPS"); if (!label || label.length > 80 || /[\r\n]/.test(label)) throw new Error("REPOSITORY_LABEL must be 1–80 single-line characters"); if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository ?? "")) throw new Error("Invalid REPOSITORY"); if (!/^\d+$/.test(issueId ?? "") || !/^\d+$/.test(issueNumber ?? "") || !/^\d+$/.test(runId ?? "")) throw new Error("Invalid issue or run identity"); const issueUrl = `https://github.com/${repository}/issues/${issueNumber}`; const digest = createHash("sha256").update(`${repository}:${issueId}:${runId}`).digest("hex"); const eventId = `github-issue-closed:${digest}`; const payload = { action: null, title: "GitHub issue closed", message: `${label} · issue #${issueNumber}`, data: { issue_number: Number(issueNumber), issue_url: issueUrl }, correlation_id: eventId, }; const response = await fetch(webhookUrl, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", "Idempotency-Key": eventId }, body: JSON.stringify(payload), redirect: "error", signal: AbortSignal.timeout(10000), }); if (!response.ok) throw new Error(`PingRoom returned HTTP ${response.status}`); const accepted = await response.json(); if (accepted.success !== true) throw new Error("PingRoom did not accept the issue alert"); console.log(`PingRoom accepted ${accepted.notification_id}`); } main().catch((error) => { console.error(error.message); process.exit(1); }); NODE
GitHub's issues event reference says the workflow file must exist on the default branch. The closed activity avoids runs for ordinary edits, comments, assignments, and label changes. GitHub's expression reference documents the same contains(github.event.issue.labels.*.name, 'label') pattern used here.
permissions: {} removes repository access from the workflow's GITHUB_TOKEN. The job does not check out code, install a dependency, call GitHub's API, or execute issue content. Its only external credential is the PingRoom URL supplied through the repository secret.
Keep the phone copy deliberately small
The workflow sends a maintainer-written repository label and the numeric issue number. It does not send the issue title, body, labels, comments, closer, author, or repository contents.
The link returns an authorized team member to GitHub for the real record. If the repository name itself is sensitive, use a neutral REPOSITORY_LABEL such as Mobile project. Remember that the issue URL still contains the repository path and will be visible to Room members.
Do not put customer information, security findings, credentials, private incident details, or unreleased plans in the label or notification copy. A lock-screen preview is the wrong place for them.

Pingo the Pigeon, PingRoom's fictional Chief Marketing Officer, is watching the untagged tile remain outside the gate. He has no repository access, private-Room access, or authority to close an issue or send its alert. A human maintainer chooses the label, workflow, Room, recipients, copy, and failure policy.
These AI-generated editorial illustrations feature approved PingRoom avatars and Pingo. They are not GitHub or PingRoom screenshots and do not depict customers.
Give one closure one replay identity
The idempotency key is a SHA-256 digest of the repository, GitHub's numeric issue ID, and the workflow run ID. Rerunning the same workflow run uses the same key and body, so PingRoom can return the original accepted result instead of creating another Ping.
If an issue is reopened and later closed again, GitHub creates a new workflow run. That new closure gets a new identity and can send a new alert. Do not reuse the first key with changed copy; PingRoom rejects a changed body under an existing idempotency key.
The Ping step is non-blocking because GitHub already owns the closed state. continue-on-error: true leaves the issue record untouched if the shared alert cannot be sent. A team that needs guaranteed escalation should keep its durable project record and established paging path.
Test the boundary before using it
Use a harmless test issue in the selected repository.
- Close an issue without
shared-status. The job should be skipped. - Add
shared-statusto another test issue, then close it. One GitHub issue closed Ping should appear. - Open the link as a Room member. GitHub should still enforce repository access.
- Rerun the same workflow. It should not create a second Ping.
- Reopen and close the issue again. The new closure should create a fresh alert.
- Remove the secret and repeat with another test issue. Only the non-blocking notification step should fail.
For this article, we ran the exact embedded Node request against a stateful local mock of PingRoom's documented contract. The checks covered acceptance, exact replay, changed-body conflict, a fresh closure, required headers, field limits, privacy exclusions, insecure URLs, redirects, HTTP rejection, malformed JSON, and missing configuration. They did not use a live GitHub repository, PingRoom Room, phone, or notification receipt.
An HTTP 200 with success: true means PingRoom accepted and queued the Ping. Connectivity, notification permission, Focus settings, Room mutes, and operating-system policy still decide what appears on each phone. Silence is not evidence that an issue stayed open; GitHub remains the record.
If a labeled closure deserves a shared signal, create the destination Room and webhook, add the secret, and test with non-sensitive copy. For a workflow's terminal result instead, use the separate GitHub Actions notification recipe. For review states, keep using the pull-request review guide.
GitHub is a trademark of GitHub, Inc. PingRoom is not affiliated with or endorsed by GitHub.
PingRoom
Short-form communication.


