Field Notes

Receive GitHub webhooks on your Mac without port forwarding

GitHub webhooks cannot reach a Mac on home Wi-Fi. The hooks.watchflows.app relay can, with no port forwarding or tunnel, and it is how we ship our own releases.

The flow run with the body GitHub sends for a workflow_run event, entering where Public Webhook hands it on: a failed run notifies and logs, a successful one stops at the Condition.

Webhooks assume you have a server. GitHub, Stripe and Linear all want a public HTTPS address to POST to, and a Mac on home Wi-Fi has none. The usual answers each cost something. Port forwarding opens your router, a free tunnel changes its URL on every restart, and a VPS is one more machine to patch.

We hit this with our own releases. Signed macOS builds need a Mac, and GitHub's hosted macOS runners bill at ten times the Linux rate. We already had a Mac mini on a shelf. What we needed was for git push origin v1.54.0 to start a build on it.

How Watchflows ships its own releases

The mini runs the production app with one flow imported, Release on Tag:

  1. A tag push makes GitHub send a create event to https://hooks.watchflows.app/<channel-id>.
  2. The relay queues it and pushes it down the mini's open connection.
  3. Public Webhook checks the GitHub signature on the mini.
  4. A Condition requires body.ref_type equals tag and body.ref matches ^v[0-9]+\.[0-9]+\.[0-9]+$, so a new branch never starts a build.
  5. Run Script calls release_local.sh, which reads the tag out of $PAYLOAD_body itself. Nothing from the webhook is templated into the shell.
  6. The script prints a JSON summary, and two Conditions on output.status send a Shipped or Failed notification. locked and skipped-already-built stay quiet.
Fig. 1The real Release on Tag flow: signature check, tag filter, the build script, and a notification for each outcome.

A second flow checks GitHub for new tags every ten minutes as a safety net, and a shared ledger of built tags keeps the two from building the same release twice.

Get a notification when a GitHub Actions run fails

You probably do not ship a Mac app from a closet. You probably do push code and find out 20 minutes later, from an email, that CI failed. This flow posts a notification the moment a GitHub Actions run fails, opens the run's page when you click it, and appends a line to a log.

Fig. 2Public Webhook, a Condition, and two actions on its Yes outlet.

GitHub (Public Webhook)

Select the node and press Create Public URL. The How to Use section shows the full address. Verify signature is already on in this flow (a new node starts with it off) and Signature Scheme is GitHub. Paste a long random string into Signing Secret (openssl rand -hex 32 makes one).

Fig. 3The webhook inspector on a Mac without the relay. With an owned license or Cloud, the Endpoint URL card has Create Public URL, which fills in the address.

Then in GitHub, under the repository's Settings ▸ Webhooks ▸ Add webhook:

FieldValue
Payload URLhttps://hooks.watchflows.app/<channel-id>
Content typeapplication/json
SecretThe same string
EventsLet me select individual events ▸ Workflow runs

The content type matters. GitHub defaults to form encoding, and the trigger only parses the body into an object when the sender says application/json. Otherwise body arrives as one string and every rule below reads nothing.

Failed run?

GitHub sends three workflow_run events for every run: requested, in_progress and completed. The Condition keeps one of them with two rules under Match All: body.action equals completed, and body.workflow_run.conclusion equals failure. A cancelled run has the conclusion cancelled and goes nowhere.

Fig. 4Two rules, both required. Everything else leaves by the No outlet, which is not wired.

Tell me, and CI log

The Notification's title is {{body.repository.name}}: {{body.workflow_run.name}} failed, the body is the branch and {{body.workflow_run.display_title}}, and Action URL is {{body.workflow_run.html_url}}, so a click opens the failed run. Log to File appends {{timestamp}}, the repo, the branch and the URL to ~/logs/ci-failures.log.

Fig. 5The notification from that run. Clicking it opens the failed run on GitHub.

Both actions hang off the Condition's Yes outlet rather than one after the other. Notification replaces the payload with notified, title and body, so anything wired after it would see no body.repository at all.

Paste into the Flow Builder

Give me a public webhook that verifies GitHub signatures. When a workflow_run event says a run completed with conclusion failure, notify me with the repo, workflow name and branch, open the run's page when I click the notification, and append a line to ~/logs/ci-failures.log.

What the relay sees, and what it cannot do

The relay is a pipe. It forwards the body byte for byte, with the headers, method and path, and it keeps the delivery in an encrypted queue only until your Mac confirms it. Channel records hold hashes of the token and license, never the values, and the gateways run with logging off, so bodies never reach a log line. The full list is on What Transits Our Servers.

Two limits follow from that design:

The address itself is the credential. Exporting a flow empties the channel id and the signing secret, which is why the download above has a blank secret. If the address leaks, Regenerate replaces it at once and Revoke kills it.

Sleep, duplicates and rate limits

The relay comes with owning the app ($49 once) or a Watchflows Cloud plan, and needs the Enable public relay switch in Settings ▸ Webhooks.

Fig. 6Settings ▸ Webhooks on a Mac without Cloud. The local server above takes webhooks from this Mac only; the Public Relay section at the bottom is where the Enable public relay switch appears once Cloud is on.

Variations

What's in this flow

One trigger. Nodes are listed in the order a run reaches them; the canvas above shows where it branches.

Download this flow
  1. Public WebhookGitHub
  2. ConditionFailed run?
  3. NotificationTell me
  4. Log to FileCI log

Run this one yourself

Download the flow, double-click it, and Watchflows opens it on the canvas. Everything in this note is on the 14-day free trial.