A secure, high-performance webhook server built in Rust for processing Git server webhooks (Forgejo, Gitea, GitHub) with flexible routing strategies. Perfect for automating deployments, builds, and other CI/CD tasks.
- π‘οΈ Security First: HMAC-SHA256 signature verification with constant-time comparison
- π High Performance: Built with Actix Web for excellent performance and memory safety
- π Flexible Routing: Multiple routing strategies (path-based, repository-based, event-based, single)
- π¦ Container Ready: Designed for containerized deployments with Docker/Podman
- π Rich Context: Passes comprehensive webhook context to your scripts
- π Observability: Structured logging with health check endpoint
- π§ Configuration: Environment variable based configuration with validation
git clone <repository-url>
cd rusty-hook
cargo build --releaseCreate a .env file:
# Required
WEBHOOK_SECRET=your-super-secure-webhook-secret-here #(Minimum 16 characters)
# Routing Strategy
ROUTING_STRATEGY=path
# Path-based routing executables
WEBHOOK_EXEC_BLOG=./scripts/deploy-blog.sh
WEBHOOK_EXEC_DOCS=./scripts/deploy-docs.sh
WEBHOOK_EXEC_API=./scripts/deploy-api.sh
# Server Configuration
BIND_ADDRESS=127.0.0.1:8080
WEBHOOK_ROUTE=/webhook
MAX_PAYLOAD_SIZE=2097152 #2MB#!/bin/bash
# scripts/deploy-blog.sh
set -euo pipefail
echo "Deploying ${WEBHOOK_REPOSITORY} from ${WEBHOOK_REF}"
echo "Git Event: ${WEBHOOK_EVENT}, Private repo: ${WEBHOOK_PRIVATE_REPO}"
echo "Pusher name: ${WEBHOOK_PUSHER_NAME}, Pusher Email: ${WEBHOOK_PUSHER_EMAIL}"
echo "Route Key: ${WEBHOOK_ROUTE_KEY}, Git Action: ${WEBHOOK_ACTION}"
# Your deployment logic here
if [[ "$WEBHOOK_REF" == "refs/heads/main" ]]; then
echo "Deploying to main branch..."
# hugo build, docker deploy, etc.
fi# Development
cargo run
# Production
./target/release/rusty-hookSet up webhooks pointing to:
- Blog:
https://yourserver.com/webhook/blog - Docs:
https://yourserver.com/webhook/docs - API:
https://yourserver.com/webhook/api
Route webhooks based on URL path. Each path maps to a specific executable.
ROUTING_STRATEGY=path
WEBHOOK_EXEC_BLOG=./scripts/deploy-blog.sh
WEBHOOK_EXEC_DOCS=./scripts/deploy-docs.sh
WEBHOOK_EXEC_API=./scripts/deploy-api.shWebhook URLs:
/webhook/blogβdeploy-blog.sh/webhook/docsβdeploy-docs.sh/webhook/apiβdeploy-api.sh
Route based on repository name from the webhook payload. (Replace "/" and "-" with "_" when creating variable names)
ROUTING_STRATEGY=repository
# Variable name format: WEBHOOK_EXEC_<Optional<USER>><REPOSITORY>
WEBHOOK_EXEC_MYAPP=./scripts/deploy-myapp.sh
# Replace "-" with "_" for hyphenated repo name: cool-app
WEBHOOK_EXEC_COOL_APP=./scripts/deploy-cool-app.sh
# Replace "/" with "_" for full repo name with user: username/repo-name
WEBHOOK_EXEC_USERNAME_REPO_NAME=./scripts/deploy-user-specific-payload.shRoute based on Git event type (push, pull_request, release, etc.).
ROUTING_STRATEGY=event
WEBHOOK_EXEC_PUSH=./scripts/deploy.sh
WEBHOOK_EXEC_PULL_REQUEST=./scripts/test.sh
WEBHOOK_EXEC_RELEASE=./scripts/release.shUse one script for all webhooks (default behavior).
ROUTING_STRATEGY=single
EXECUTABLE_PATH=./scripts/universal-handler.shYour scripts receive rich context via environment variables:
| Variable | Description | Example |
|---|---|---|
WEBHOOK_EVENT |
Git event type | push, pull_request, release |
WEBHOOK_REPOSITORY |
Repository name | my-blog, documentation |
WEBHOOK_PUSHER_NAME |
Name of Git pusher | John Doe, Alice Smith |
WEBHOOK_PUSHER_EMAIL |
Email of Git pusher | jdoe@example.com, alice@email.com |
WEBHOOK_REF |
Git reference | refs/heads/main, refs/tags/v1.0.0 |
WEBHOOK_ROUTE_KEY |
Webhook route identifier | blog, docs, api |
WEBHOOK_PRIVATE_REPO |
Is repository private | true, false |
WEBHOOK_ACTION |
Git action (if present) | opened, closed, merged |
version: '3.8'
services:
webhook-server:
build:
context: .
dockerfile: Dockerfile
container_name: git-webhook
restart: unless-stopped
environment:
- ROUTING_STRATEGY=path
- WEBHOOK_SECRET=${WEBHOOK_SECRET}
- BIND_ADDRESS=0.0.0.0:8080
- WEBHOOK_EXEC_BLOG=./scripts/deploy-blog.sh
- WEBHOOK_EXEC_DOCS=./scripts/deploy-docs.sh
volumes:
- ./scripts:/opt/scripts:ro
- ./web-content:/var/www:rw
- ./git-repos:/opt/repos:rw
ports:
- "127.0.0.1:8080:8080"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3- Signature Verification: Always verifies HMAC-SHA256 signatures using constant-time comparison
- Content Type Validation: Only accepts
application/jsonpayloads - Rate Limiting: Configure reverse proxy rate limiting
- HTTPS: Always use HTTPS in production
- Minimal Permissions: Run with minimal filesystem permissions
- Input Validation: All environment variables are validated on startup
| Variable | Required | Default | Description |
|---|---|---|---|
WEBHOOK_SECRET |
β | - | Secret key for HMAC verification (min 16 chars) |
ROUTING_STRATEGY |
β | single |
Routing strategy: path, repository, event, single |
EXECUTABLE_PATH |
β | ./scripts/deploy.sh |
Default executable for single strategy |
WEBHOOK_EXEC_<KEY> |
β | - | Executable mappings for routing strategies |
BIND_ADDRESS |
β | 127.0.0.1:8080 |
Server bind address |
WEBHOOK_ROUTE |
β | /webhook |
Base webhook route |
MAX_PAYLOAD_SIZE |
β | 1048576 |
Maximum payload size in bytes |
X-Hub-Signature-256(GitHub, Forgejo)X-Forgejo-Signature(Forgejo)X-Gitea-Signature(Gitea)X-Forgejo-Event/X-Gitea-Event(Event type)
curl http://localhost:8080/health
# {"status":"healthy","service":"rusty-hook"}Uses structured logging with configurable levels:
# Set log level
export RUST_LOG="rusty_hook=debug"
# JSON structured logs
export RUST_LOG_FORMAT=jsonSignature Verification Failed
- Verify webhook secret matches exactly
- Check webhook is configured for
application/jsoncontent type - Ensure HTTPS is used if webhook requires it
Script Execution Failed
- Verify script file exists and is executable (
chmod +x) - Check script shebang line (
#!/bin/bash) - Review script output in server logs
No Handler Configured
- Verify
WEBHOOK_EXEC_*environment variables are set - Check routing strategy matches your webhook URLs
- Ensure executable files exist at specified paths
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Built with Linux, β€οΈ and Coffee.