Skip to content

Scope extension: route inbound HTTP webhooks (Statuspage, GitHub org events) through hooksmith #114

Description

@bdelanghe

Intent (maintainer's, recorded from a working session 2026-08-07)

"would other webhooks such as github help us out? someday these should get routed through hooksmith"

The org just stood up its service-status layer (infra/cloudflare/status, live at status.bounded.tools), and its POST /webhook/statuspage route is currently its own webhook receiver. That is tolerable only because Statuspage webhooks are unsigned and therefore untrusted by design — the payload is discarded and the event is a mere refresh poke, so there is nothing to verify and nothing to route. The moment a signed webhook source is worth consuming, that excuse expires.

What this issue proposes

Extend hooksmith from git hook events to inbound HTTP webhook events, so receiver infrastructure exists once:

  • One place that verifies signatures. GitHub webhooks are HMAC-signed (X-Hub-Signature-256). Ten workers each verifying HMAC with their own copy of the secret is ten copies of security-critical code drifting apart.
  • One place that holds webhook secrets. Same custody argument the org just finished making for the mock lever key (infra#218): a secret with one holder and an audited release path beats a shared string.
  • One place that fans events out to consumers. The status worker, the front desk, and whatever comes next should be pure consumers with authenticated inboxes — the same shape POST /mock already has (bearer-gated), rather than each growing an open one.

Why hooksmith rather than a new thing

Honest framing: hooksmith today is a git hook engine — git → hook-event → policy engine → hook-result → exit, host-native + wasm32-wasip2 components. This is a scope extension, not a wiring task. But the architecture is exactly the right one to extend:

  • The core contract already is event stream → pure policy engine → verdict. An HTTP webhook is another event stream; "verify signature, classify, route" is another pure policy given (headers, body, secret) as inputs.
  • The engine's purity (no I/O in hook_engine) is precisely what makes signature verification and routing decisions testable in isolation — the property that matters most in the one component every external event transits.
  • The wasm component build means the same policy engine can embed in a Cloudflare Worker (where the org's receivers live: the broker and status worker precedents) without a second implementation.

What the webhook case adds that the git case never needed: an HTTP listener/host (likely a Worker embedding the component), HMAC verification, secret custody (the broker's /vendor tier or Worker Secrets — see infra/cloudflare/broker), delivery/fan-out with receipts.

First consumers, in value order

  1. GitHub org webhooks → observed platform health. workflow_run/workflow_job failure rate across unrelated repos is a leading indicator of platform incidents — during githubstatus qcvjkzcs7j74 (2026-08-06), "job not acquired by hosted runner" was visible in our own runs well before the status page went red. Feed the status layer's splat map (github.actions et al.) with observed health beside vendor-declared health.
  2. The existing Statuspage poke. Move POST /webhook/statuspage behind the router; the status worker's inbox shrinks to an authenticated "refresh" call and the last unsigned inbox disappears.
  3. Front-desk events (issues, PRs, project board) — same routing problem, different consumer.

Non-goals for now

Nothing here is urgent. The Statuspage-direct path shipped in infra#218 is correct until this exists: unsigned poke, payload never parsed, arrival receipts at GET /webhook/last. This issue exists so the "someday" is written down where the code will live, with the constraints already learned (verify-don't-trust, receipts over silence, secrets nobody holds).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions