Webhook Ingestion Flow

The dashboard is driven entirely by GitHub webhook events — it does not poll the GitHub API in normal operation.

Setup

A webhook must be configured on the source GitHub repo (e.g. AkerBP/wise) to send events to:

POST https://<your-dashboard>/api/webhook

The webhook secret must match the WEBHOOK_SECRET env var on the dashboard.

Required event types

EventPurpose
pull_requestIngest PR open/merge/close and fetch firstCommitAt
projects_v2_itemDetect when an issue moves to the “released” column → stamp deployedAt

Request flow

GitHub → POST /api/webhook
           ↓
    webhookMiddleware      validates X-Hub-Signature-256 HMAC
           ↓
    webhookController     reads X-GitHub-Event header, delegates
           ↓
    webhookService        routes by event type
      ├── pull_request    → pullRequestService.ingest()
      └── projects_v2_item → deploymentService.handleRelease()

pull_request event

Fires on opened, closed (merged), and synchronize.

On opened:

  1. Upsert the PR record in pull_requests.
  2. Fetch the branch’s commit list from GitHub REST API (GET /repos/{owner}/{repo}/pulls/{number}/commits).
  3. Take the earliest commit’s commit.author.date as firstCommitAt.

On closed with merged: true:

  1. Set mergedAt on the PR record.

projects_v2_item event (deployment signal)

Fires when any field on a GitHub Projects item changes.

The handler:

  1. Checks action === 'edited' and changes.field_value.field_name === 'Status'.
  2. Reads changes.field_value.to.name (the new status value).
  3. Compares to the configured released_column_name from app_settings (default "Released").
  4. If it matches:
    • Resolves the linked issue number from content_url in the event payload.
    • Finds all merged PRs linked to that issue (via Closes #N keywords, stored during PR ingestion).
    • Stamps deployedAt = event.projects_v2_item.updated_at on each matched PR.

Signature validation

Every incoming request is validated with HMAC-SHA256:

X-Hub-Signature-256: sha256=<hex>

The signature is computed over the raw request body using WEBHOOK_SECRET. Requests that fail validation are rejected with 401.

Env vars

VarPurpose
WEBHOOK_SECRETGitHub webhook secret for HMAC validation
GH_TOKENPAT with repo + read:project scope — used to resolve issue→PR links via GraphQL
GH_OWNEROwner of the source repo (e.g. AkerBP)
GH_REPOName of the source repo (e.g. wise)