A centralized API service for consuming GitHub webhook events and routing them to downstream service endpoints based on configurable rules.
graph TD
GitHubWebhook-->|POST /|ExpressAPI
ExpressAPI-->|webhookHandler|lib
lib-->|getTargetUrl|targetRoutes
targetRoutes-->|Returns target URL|lib
lib-->|sendWebhook|targetURL
This application acts as a webhook dispatcher that:
- Receives GitHub webhook events at a centralized endpoint
- Validates webhook signatures for security
- Routes events to appropriate downstream services based on repository configuration
- Provides reliable webhook forwarding with proper error handling
- Node.js: Version 20.0.0 or higher
- npm: Version 8.0.0 or higher
- A GitHub repository with webhook configuration
- Target services to receive forwarded webhooks
-
Clone and install dependencies:
git clone https://github.com/collinmcneese/github-webhook-dispatcher.git cd github-webhook-dispatcher npm install
-
Set required environment variables:
export WEBHOOK_DISPATCHER_WEBHOOK_SECRET=your_github_webhook_secret export WEBHOOK_DISPATCHER_ROUTE_FILE=./routes.toml
-
Create a routes configuration file (see example):
[your-org] target = "https://your-service.example.com/webhook"
-
Start the application:
npm run dev
-
Configure your GitHub webhook to point to
http://your-server:3000/
- src/app.js: The main Express application file. This file contains the Express API server and webhook handler.
- src/lib.js: The main library file. This file contains the webhook handler and functions for retrieving the target URL for a given GitHub repository.
- src/config.js: The main configuration file. This file contains the configuration for the Express API server and webhook handler.
- openapi.yaml: The OpenAPI specification file. This file contains the OpenAPI specification for the Express API server.
- Always use a strong webhook secret: Generate a cryptographically secure random string
- Keep secrets secure: Store the webhook secret in environment variables, never in code
- Rotate secrets regularly: Update webhook secrets periodically for better security
- Use HTTPS in production: Always deploy behind HTTPS to protect webhook payloads
- Enable SSL verification: GitHub recommends keeping SSL verification enabled (default)
- IP allowlisting: Restrict access to GitHub's webhook IP ranges using the
/meta
API endpoint - Rate limiting: The application includes built-in rate limiting to prevent abuse
- Response timing: Responds within GitHub's 10-second timeout requirement
This application follows GitHub's webhook best practices:
- Use strong webhook secrets: Generate cryptographically secure random strings
- HTTPS in production: Always deploy behind HTTPS with SSL verification enabled
- IP allowlisting: Restrict access to GitHub's webhook IP ranges (see
/meta
endpoint) - Quick response times: Responds within GitHub's 10-second requirement
- Event validation: Validates event types and webhook signatures
- Asynchronous processing: Consider implementing a queue system for heavy processing
- Event filtering: Only subscribe to the webhook events you actually need
- Replay protection: Uses
X-GitHub-Delivery
headers to prevent replay attacks - Error handling: Proper error responses and logging for failed deliveries
# Generate a secure webhook secret (macOS/Linux)
openssl rand -hex 32
# Use environment files for local development
echo "WEBHOOK_DISPATCHER_WEBHOOK_SECRET=$(openssl rand -hex 32)" > .env
Problem: Error: WEBHOOK_DISPATCHER_WEBHOOK_SECRET is required
Solution: Ensure the webhook secret environment variable is set before starting the application.
Problem: Error: Cannot read routes file
Solution: Verify the routes file path and ensure the file exists and is readable.
Problem: Error: Invalid webhook signature
Solution: Check that the webhook secret matches between GitHub webhook settings and your environment variable.
Problem: Webhooks not being forwarded Solution: Verify your routes configuration matches your GitHub repository owner/name exactly (case-sensitive).
Enable debug logging by setting the environment variable:
export DEBUG=webhook-dispatcher:*
npm run dev
The application exposes a health check endpoint:
curl http://localhost:3000/health
The following environment variables configure the application:
Warning
Breaking change in version 3.0.0: The environment variable names have been updated to use the prefix WEBHOOK_DISPATCHER_
instead of WHDISPATCHER_
.
Variable | Required | Default | Description |
---|---|---|---|
WEBHOOK_DISPATCHER_PORT |
No | 3000 |
The port number for the Express API server |
WEBHOOK_DISPATCHER_WEBHOOK_SECRET |
Yes | - | Secret token to verify GitHub webhook events. Must match GitHub repository webhook settings |
WEBHOOK_DISPATCHER_ROUTE_FILE |
Yes | - | Path to the routes configuration file (TOML, JSON, or YAML format) |
export WEBHOOK_DISPATCHER_PORT=8080
export WEBHOOK_DISPATCHER_WEBHOOK_SECRET=abc123
export WEBHOOK_DISPATCHER_ROUTE_FILE=/path/to/routes.toml
The routes configuration file maps GitHub repository owners and names to target URLs where webhooks should be forwarded.
The application supports TOML (recommended), JSON, and YAML formats for the routes file.
- Top-level objects: GitHub repository owners or organizations
- Owner-level target: Default URL for all repositories under an owner/organization
- Repository-level targets: Specific URLs for individual repositories (overrides owner-level target)
- Event filtering: Optional
events
array to specify which GitHub webhook event types to forward
You can optionally specify which GitHub webhook event types should be forwarded by adding an events
array to your route configuration. If no events
array is specified, all webhook events will be forwarded (default behavior).
Common GitHub webhook event types include:
push
- Repository push eventspull_request
- Pull request events (opened, closed, etc.)issues
- Issue events (opened, closed, etc.)issue_comment
- Comments on issues or pull requestsrelease
- Release eventsworkflow_run
- GitHub Actions workflow eventsstar
- Repository starring eventsfork
- Repository fork events
For a complete list of available event types, see the GitHub webhook documentation.
TOML Example
# Top level objects are GitHub repository owners or organizations
[owner1]
# Default target URL for all repositories owned by owner1
target = "https://example.com/owner1"
# Only forward push and pull_request events for this org
events = ["push", "pull_request", "issues"]
# Specific target for owner1/repo1
[owner1.repo1]
target = "https://example.com/owner1/repo1"
# Only forward specific events for this repo
events = ["push", "release", "workflow_run"]
# Specific target for owner1/repo2
[owner1.repo2]
target = "https://example.com/owner1/repo2"
# Forward all events (events array not specified)
[owner2]
# Only specific repository targets, no default
[owner2.special-repo]
target = "https://special-service.example.com/webhook"
# Only forward star and fork events
events = ["star", "fork"]
JSON Example
{
"owner1": {
"target": "https://example.com/owner1",
"events": ["push", "pull_request", "issues"],
"repo1": {
"target": "https://example.com/owner1/repo1",
"events": ["push", "release", "workflow_run"]
},
"repo2": {
"target": "https://example.com/owner1/repo2"
}
},
"owner2": {
"special-repo": {
"target": "https://special-service.example.com/webhook",
"events": ["star", "fork"]
}
}
}
YAML Example
owner1:
target: https://example.com/owner1
events: ["push", "pull_request", "issues"]
repo1:
target: https://example.com/owner1/repo1
events: ["push", "release", "workflow_run"]
repo2:
target: https://example.com/owner1/repo2
owner2:
special-repo:
target: https://special-service.example.com/webhook
events: ["star", "fork"]
For local development with automatic restart on file changes:
npm install # if not already done
npm run dev
For production deployment:
npm install --production
npm run build
npm start
Build and run the application in a Docker container:
# Build the Docker image
docker build -t webhook-dispatcher .
# Run with environment variables
docker run \
-p 3000:3000 \
-e WEBHOOK_DISPATCHER_WEBHOOK_SECRET=your_secret_here \
-e WEBHOOK_DISPATCHER_ROUTE_FILE=/app/routes.toml \
-v $(pwd)/routes.toml:/app/routes.toml:ro \
webhook-dispatcher
Create a docker-compose.yml
file for easier management:
version: '3.8'
services:
webhook-dispatcher:
build: .
ports:
- "3000:3000"
environment:
- WEBHOOK_DISPATCHER_WEBHOOK_SECRET=your_secret_here
- WEBHOOK_DISPATCHER_ROUTE_FILE=/app/routes.toml
volumes:
- ./routes.toml:/app/routes.toml:ro
restart: unless-stopped
Then run with:
docker-compose up -d
-
Write out full docs for README✅ Completed - Add queueing mechanism for asynchronous processing (GitHub best practice)
- Add ability to poll GitHub and find failed event notifications for retransmission
-
Implement webhook event filtering based on event types✅ Completed - Add replay attack protection using
X-GitHub-Delivery
headers - Add option to read routes from an alternate source instead of local file
- Add cloud-native infrastructure setup for all components for Azure and AWS
- Add comprehensive test suite
- Add metrics and monitoring endpoints