Webhook Server is a lightweight Flask (python) server to listen for incoming webhooks and process them based on predefined rules.
This project is meant to be a personal project (shared as it might be useful example) to keep track of incoming webhooks and trigger custom bash scripts. It aims to run as a systemd service on a Linux server, listening for incoming webhooks from platforms like GitHub, GitLab, or Bitbucket. Upon receiving a webhook, it triggers a bash script to handle the event.
Unfortuantely depending on script, user has to be priveleged to run certain commands like restarting services, pulling from git repos etc.
Github Webhook ----> Webhook Server ----> Bash Script
- ✅ GitHub webhook integration with HMAC signature verification
- ✅ Password-protected manual trigger endpoint
- ✅ Interactive OpenAPI/Swagger documentation
- ✅ Logging with loguru
- ✅ Background script execution
- ✅ Health check endpoint
pip install -e .Copy .env.example to .env and configure:
WEBHOOK_SECRET=your-github-webhook-secret
PASS=your-manual-trigger-password
SCRIPTS_DIR=./scripts
PORT=9000
LOG_DIR=/var/log/webhook-server
ALLOWED_ORIGINS=http://localhost:9000,https://your-server.comALLOWED_ORIGINS should include the exact origin where you open Swagger UI (/docs).
If it is missing or mismatched, browser-based requests may fail with TypeError: Failed to fetch.
# Development
uv run python -m app.main
# Production with gunicorn
gunicorn -w 4 -b 0.0.0.0:9000 src.main:appGET /health
Check server health and version.
curl http://localhost:9000/healthResponse:
{
"status": "OK",
"version": "1.0.0",
"timestamp": "2026-02-01T12:00:00Z"
}POST /{project}
Receive GitHub webhook events (requires valid HMAC signature).
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=..." \
-d '{"ref": "refs/heads/main"}' \
http://localhost:9000/myprojectPOST /manual/{project}
Manually trigger build scripts with password authentication.
Project name matching supports a normalized alias format. For example, both
f1-board and f1board can match scripts/f1-board.sh.
curl -X POST \
-H "X-Password: your_password" \
http://localhost:9000/manual/myprojectcurl -X POST \
"http://localhost:9000/manual/myproject?password=your_password"Response:
{
"status": "OK",
"project": "myproject",
"trigger": "manual"
}.
├── main.py # Main Flask application
├── requirements.txt # Python dependencies
├── .env # Environment configuration (create this)
├── scripts/ # Build scripts directory
│ ├── myproject.sh
│ └── another-project.sh
└── logs/ # Log files (auto-created)
└── webhook-server.log
| Variable | Required | Default | Description |
|---|---|---|---|
WEBHOOK_SECRET |
No | None | GitHub webhook secret for HMAC verification |
PASS |
Yes* | None | Password for manual trigger endpoint |
SCRIPTS_DIR |
No | ./scripts |
Directory containing build scripts |
LOG_DIR |
No | ./logs |
Directory for log files |
PORT |
No | 9000 |
Server port |
*Required if using the /manual/{project} endpoint
- Configure webhook secret in GitHub repository settings
- Set the same secret in your
WEBHOOK_SECRETenvironment variable - Requests without valid signatures are rejected with 403
- Always use HTTPS in production
- Password is verified using constant-time comparison
- Use the
X-Passwordheader instead of query parameters (headers are less likely to be logged) - Keep your
PASSenvironment variable secret
Logs are written to:
- Console (INFO level and above)
- File at
{LOG_DIR}/webhook-server.log(DEBUG level)- Rotated at 5 MB
- Retained for 30 days
- Compressed with gzip
-
Copy the project to
/opt/webhook-server:sudo cp -r . /opt/webhook-server cd /opt/webhook-server
-
Create virtual environment and install dependencies:
sudo python3 -m venv .venv sudo .venv/bin/pip install -e . -
Create log directory:
sudo mkdir -p /var/log/webhook-server sudo chown www-data:www-data /var/log/webhook-server
-
Configure environment:
sudo cp .env.example .env sudo nano .env # Edit with your values -
Install/Update and Enable the service:
sudo cp webhook-server.service /etc/systemd/system/ # requires to edit user and filepaths sudo systemctl daemon-reload sudo systemctl enable webhook-server sudo systemctl start webhook-server
-
Check status:
sudo systemctl status webhook-server sudo journalctl -u webhook-server -f
- Go to your GitHub repo → Settings → Webhooks → Add webhook
- Set Payload URL to
https://your-server.com/webhook/<project-name>(e.g.,https://server.giraycoskun.dev/webhook/my-app) - Set Content type to
application/json - Set Secret to match your
WEBHOOK_SECRET - Select events to trigger the webhook
- Start the server
- Open http://localhost:9000/docs
- Click on any endpoint to expand it
- Click "Try it out"
- Fill in parameters and click "Execute"
Health check:
curl http://localhost:9000/healthManual trigger:
curl -X POST \
-H "X-Password: mypass123" \
http://localhost:9000/manual/f1boardGet OpenAPI spec:
curl http://localhost:9000/openapi.json | jqMIT