Guide for testing the GitHub webhook endpoint that receives release events and syncs them to the database.
| Detail | Value |
|---|---|
| Endpoint | POST /webhooks/github |
| Auth | HMAC-SHA256 signature via X-Hub-Signature-256 header |
| Events | release (published, created, edited, prereleased, released, deleted) |
| Ignored | All other GitHub event types are acknowledged with 200 OK |
When a release event arrives for a repository tracked by a product (ProductRepository), the webhook upserts a GitHubRelease row. If the repository is not tracked, the event is silently ignored.
Set GITHUB_WEBHOOK_SECRET in your .env file. This must match the secret configured in your GitHub App's webhook settings:
# apps/lfx-changelog/.env
GITHUB_WEBHOOK_SECRET=your-github-webhook-secretThe script below sends a simulated release event to your local server. It computes the correct HMAC-SHA256 signature so the middleware accepts the request.
#!/usr/bin/env bash
set -euo pipefail
# ── Configuration ─────────────────────────────────────────────────────
WEBHOOK_URL="http://localhost:4204/webhooks/github"
SECRET="your-github-webhook-secret" # Must match GITHUB_WEBHOOK_SECRET in .env
REPO_FULL_NAME="linuxfoundation/lfx-changelog" # Use a repo tracked by a product
# ── Payload ───────────────────────────────────────────────────────────
PAYLOAD=$(cat <<'JSON'
{
"action": "published",
"release": {
"id": 999001,
"tag_name": "v1.0.0-test",
"name": "Test Release v1.0.0",
"html_url": "https://github.com/REPO_FULL_NAME/releases/tag/v1.0.0-test",
"body": "## What's Changed\n\n- Added webhook testing guide\n- Fixed release sync\n",
"draft": false,
"prerelease": false,
"published_at": "2026-03-02T12:00:00Z",
"author": {
"login": "test-user",
"avatar_url": "https://avatars.githubusercontent.com/u/1?v=4"
}
},
"repository": {
"full_name": "REPO_FULL_NAME"
}
}
JSON
)
# Replace placeholder with actual repo name
PAYLOAD="${PAYLOAD//REPO_FULL_NAME/$REPO_FULL_NAME}"
# ── Compute HMAC-SHA256 signature ─────────────────────────────────────
SIGNATURE="sha256=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')"
# ── Send request ──────────────────────────────────────────────────────
echo "Sending release webhook to $WEBHOOK_URL"
echo "Repository: $REPO_FULL_NAME"
echo "Signature: $SIGNATURE"
echo ""
curl -s -w "\nHTTP Status: %{http_code}\n" \
-X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: release" \
-H "X-Hub-Signature-256: $SIGNATURE" \
-d "$PAYLOAD"Tracked repository (release event):
{ "ok": true }Untracked repository (release event):
{ "ok": true, "ignored": true }Non-release event (e.g., X-GitHub-Event: push):
{ "ok": true, "ignored": true }Missing or invalid signature:
{ "error": "Missing signature" } // 401
{ "error": "Invalid signature" } // 401Secret not configured on server:
{ "error": "Webhook secret not configured" } // 500To receive real GitHub webhook deliveries on your local machine, use a tunnel service like ngrok or Cloudflare Tunnel.
-
Start the dev server:
yarn start
-
Start a tunnel (ngrok example):
ngrok http 4204
Note the forwarding URL (e.g.,
https://abc123.ngrok-free.app). -
Configure your GitHub App:
- Go to your GitHub App settings > Webhook
- Set the webhook URL to
https://abc123.ngrok-free.app/webhooks/github - Set the secret to match your
GITHUB_WEBHOOK_SECRET - Subscribe to Release events
-
Trigger a release on a tracked repository (create/edit a release on GitHub).
-
Verify the webhook was received in your server logs and the release appears in the database (see Verifying Results).
-
Restore the webhook URL in your GitHub App settings when done testing.
Query the releases endpoint to see if the release was stored (requires authentication):
curl -s http://localhost:4204/api/releases \
-H "Authorization: Bearer lfx_your-api-key" | jq '.data[:3]'Browse the GitHubRelease table directly:
yarn db:studioWatch the server output for log lines like:
INFO: Upserted release from webhook repositoryId=... githubId=999001 tag=v1.0.0-test
INFO: GitHub webhook release event for untracked repository — ignoring repoFullName=...
| Symptom | Cause | Fix |
|---|---|---|
500 — secret not configured |
GITHUB_WEBHOOK_SECRET is empty or missing in .env |
Add the variable and restart the server |
401 — missing signature |
Request has no X-Hub-Signature-256 header |
Ensure the curl script includes the -H "X-Hub-Signature-256: ..." header |
401 — invalid signature |
Secret mismatch between .env and the value used to sign the payload |
Make sure both sides use the exact same secret string |
200 with ignored: true |
Repository full_name is not in the ProductRepository table |
Add the repo to a product via the admin UI or seed the database |
200 with ignored: true |
Event type is not release |
Set X-GitHub-Event: release header |
400 — empty body |
Request body is missing or not valid JSON | Ensure -d "$PAYLOAD" is included and the JSON is valid |