Skip to content

feat(api): add GitHub webhook routes for push and pull_request - #213

Open
bbornino wants to merge 1 commit into
playfulprogramming:mainfrom
bbornino:feature/205-github-webhook-routes
Open

feat(api): add GitHub webhook routes for push and pull_request#213
bbornino wants to merge 1 commit into
playfulprogramming:mainfrom
bbornino:feature/205-github-webhook-routes

Conversation

@bbornino

Copy link
Copy Markdown
Contributor

Summary

Adds two new webhook receiver routes:

  • POST /webhooks/github/push — receives GitHub's push event
  • POST /webhooks/github/pull_request — receives GitHub's pull_request event

Both verify the request's authenticity, then enqueue the raw payload as a BullMQ job for later processing. Interpreting the payload contents is explicitly out of scope for this PR — that's #206's job. apps/worker/src/tasks/ is untouched; the new WEBHOOK_PUSH/WEBHOOK_PULL_REQUEST job types have no processor yet.

Signature verification

Per GitHub's docs on validating webhook deliveries, each request's X-Hub-Signature-256 header is checked against an HMAC-SHA256 of the raw request body (not a re-serialized version of the parsed JSON — GitHub signs the exact bytes it sent, so a route-scoped addContentTypeParser captures the raw string before JSON.parse). The comparison uses crypto.timingSafeEqual, following the same constant-time pattern already used in shouldBypassRateLimit (apps/api/src/plugins/rate-limit/index.ts). A missing or invalid signature returns 401.

This required a new required env var, GITHUB_WEBHOOK_SECRET, added to packages/common/src/env.ts's EnvSchema and documented in .env.example. It gets generated when configuring the webhook in the repo's GitHub settings.

Branch filtering

The push route additionally checks ref === "refs/heads/main" per the issue's note — a push to any other branch is logged and returns 200 without enqueuing a job.

Request body schemas

Kept intentionally loose (additionalProperties: true, just validating it's a JSON object) rather than fully typing GitHub's large payload shapes — again, deferring to #206. The push route's schema does type the one field it actually reads (ref).

Job types

WEBHOOK_PUSH / WEBHOOK_PULL_REQUEST added to Tasks in packages/bullmq/src/tasks/types.ts, with minimal TaskInputs (unknown — raw payload, untyped on purpose) and TaskOutputs (object) entries. No processor implementation.

Testing

  • apps/api/src/routes/webhooks/verify-signature.test.ts — unit tests for the signature-verification helper itself (valid/invalid/tampered/missing/array-header cases)
  • apps/api/src/routes/webhooks/push.test.ts / pull-request.test.ts — route-level tests via app.inject(), mocking createJob, covering: valid signature → job enqueued, non-main branch → skipped, invalid/missing signature → 401

pnpm test:unit, pnpm run build:all, and pnpm run prettier all pass clean.

Open question for @fennifith

This route's scope overlaps conceptually with the older #7 ("Webhook to import/sync content from GitHub with the db"), which has no cross-reference to #205. Does #205 supersede #7, or are they meant to coexist (e.g. #7 being the eventual sync-on-webhook logic that #206 will implement on top of these routes)? Want to make sure we're not duplicating effort or leaving #7 stale if this closes it out in spirit.

Add POST /webhooks/github/push and POST /webhooks/github/pull_request,
each verifying the X-Hub-Signature-256 header (HMAC-SHA256 over the raw
request body, constant-time compared) before enqueuing the raw payload
as a BullMQ job. The push route additionally filters to refs/heads/main,
logging and returning early for other branches without enqueuing.

Request bodies are validated loosely (just that they're JSON objects,
plus the one `ref` field the push route itself needs) since interpreting
the payload contents is playfulprogramming#206's job, not this one's.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f3db8368-4a94-4e0e-bcbc-9651bb5a9d92


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant