Usage Guide
June 25, 2026 · View on GitHub
This guide covers day-to-day usage of HookRun: CLI commands, routing behavior, response formats, and common scenarios.
1. CLI Commands
All commands support a global -c flag to specify the config file path:
hookrun -c /path/to/config.yaml <command>
Default config path: config.yaml (current directory).
init — Initialize Configuration
# Initialize with generic template (default)
hookrun init
# Initialize with GitHub webhook template
hookrun init --template github
# Initialize with GitLab webhook template
hookrun init --template gitlab
# Overwrite existing files without prompting
hookrun init --force
Creates config.yaml and hooks/example.yaml in the current directory.
Available templates:
| Template | Description |
|---|---|
generic | Generic webhook template with token auth |
github | GitHub webhook auto-deploy with HMAC auth |
gitlab | GitLab webhook auto-deploy with token auth |
relay — View Relay Status
# Show relay status for this instance
hookrun relay status
# List registered downstream targets (requires Bearer token auth)
hookrun relay targets
# Specify registry token (defaults to relay_registry_token from config)
hookrun relay targets --token your-registry-token
A HookRun instance can have one of four relay roles:
| Role | Description |
|---|---|
upstream | Accepts downstream registrations (requires relay_registry_token) |
downstream | Registers to an upstream (requires relay_client config) |
upstream+downstream | Acts as both upstream and downstream |
none | No relay configuration |
Status output example:
Relay Status
============
Role: upstream + downstream
Upstream (Registry):
Enabled: yes
Targets: 3 registered
Max entries: 100
Max TTL: 300s
Downstream (Client):
Enabled: yes
Upstream: http://main:9000
Registered as: http://10.0.0.2:9000/webhook
Tags: prod, web
TTL: 120s
Connected: yes
Last heartbeat: 2026-06-24 14:00:00 (15s ago)
Failures: 0
Targets output example:
Registered Targets (3)
+------------------------------+------------+------+---------------------+
| URL | Tags | TTL | Last Seen |
+------------------------------+------------+------+---------------------+
| http://10.0.0.2:9000/webhook | prod, web | 120s | 2026-06-24 14:00:00 |
| http://10.0.0.3:9000/webhook | prod, api | 120s | 2026-06-24 13:59:50 |
| http://10.0.0.4:9000/webhook | staging | 60s | 2026-06-24 14:00:05 |
+------------------------------+------------+------+---------------------+
start — Start the Server
# Daemon mode (default, runs in background)
hookrun start
# Foreground mode (for debugging / terminal)
hookrun start -f
# With custom config path
hookrun start -c /etc/hookrun/config.yaml
- Daemon mode creates a PID file in
~/.hookrun/ - Foreground mode logs directly to the terminal
stop — Stop the Server
hookrun stop
- Linux/macOS: Sends SIGTERM to the process
- Windows: Uses a signal file IPC mechanism (polled every 2 seconds)
restart — Restart the Server
hookrun restart
Equivalent to stop + start. Waits up to 15 seconds for the process to stop before starting.
status — Show Server Status
hookrun status
Output example:
Status: running
PID: 12345
Port: 9000
Rules: 3 config(s)
Uptime: 2h30m15s
Started: 2026-06-11 10:00:00
If the server is stopped:
Status: stopped (no PID file)
reload — Hot-Reload Configuration
hookrun reload
- Reloads all YAML files (global + rule configs) without restarting
- Linux/macOS: Uses the HTTP reload API (
POST /_reload) - Windows: Uses signal file IPC
After reload, new rules take effect immediately for subsequent requests.
validate — Validate Configuration
hookrun validate
Output example:
Validating config: config.yaml
PASS: All configurations are valid
Server port: 9000
Webhook route: /webhook
Allow all: false
Max body size: 10 MB
Log mode: daily
Log path: ./logs
Log retention: 30 days
Config dir: ./hooks
Rule files loaded: 1
Relay registry: disabled
- github-auto-deploy (2 rules: push-to-main, tag-release) [auth: token]
Run validate before starting or reloading to catch configuration errors early.
version — Show Version Info
hookrun version
Output:
HookRun vx.y.z
Build time: 2026-06-11
Go version: go1.23.3
OS/Arch: linux/amd64
help — Show Help
# Show all available commands
hookrun help
# Show help for a specific command
hookrun help relay
hookrun help relay status
# Alternative: use --help or -h flag
hookrun start --help
hookrun relay -h
All commands support --help / -h flag to display detailed usage and examples.
2. Webhook Routing
HookRun supports two URL patterns:
Targeted Route: /webhook/{filename}
Routes directly to a specific YAML config file (matched by filename without extension).
POST /webhook/my-app
→ Matches: hooks/my-app.yaml
→ Executes: first matching rule in that file
- If the config file doesn't exist → 404
- If no rule matches → 200 "No matching rules"
Base Route: /webhook
Behavior controlled by allow_all:
allow_all | Behavior |
|---|---|
true | Iterate all configs, stop at first matching rule |
false (default) | Return 400 error (must use targeted route) |
Route Matching Examples
# Targeted: directly to hooks/frontend.yaml
POST /webhook/frontend
# Base route: iterate all YAML files
POST /webhook
Health Check
GET /health
Returns:
{"status": "ok", "uptime": "2h30m15s", "rules": 3, "version": "x.y.z"}
When relay is configured, an additional relay field is included:
{"status": "ok", "uptime": "2h30m15s", "rules": 3, "version": "x.y.z", "relay": {"role": "upstream+downstream", "upstream_targets": 3, "downstream_connected": true}}
Relay API
GET /api/relay/status — Comprehensive Relay Status (always available, no auth)
Returns the relay role and status for the current instance, regardless of whether relay is configured.
curl http://hookrun:9000/api/relay/status
Response (upstream + downstream):
{
"role": "upstream+downstream",
"upstream": {
"enabled": true,
"targets_count": 3,
"max_entries": 100,
"max_ttl": 300
},
"downstream": {
"enabled": true,
"upstream_url": "http://main:9000",
"registered_url": "http://10.0.0.2:9000/webhook",
"tags": ["prod", "web"],
"ttl": 120,
"connected": true,
"last_heartbeat": "2026-06-24T14:00:00Z",
"fail_count": 0
}
}
Response (no relay):
{
"role": "none",
"upstream": {"enabled": false},
"downstream": {"enabled": false}
}
Registry API (requires relay_registry_token)
Available only when server.relay_registry_token is configured (see Configuration Reference).
POST /api/relay/register # Register or refresh a target
DELETE /api/relay/register # Unregister a target
GET /api/relay/targets # List all registered targets
All registry API requests require the Bearer token:
curl -H "Authorization: Bearer your-registry-secret" \
http://main-hookrun:9000/api/relay/targets
3. Response Format
All webhook responses are JSON with English messages.
Success (200)
{
"code": 200,
"message": "ok",
"config": "github-auto-deploy",
"rule": "push-to-main",
"actions": 3
}
No Matching Rules (200)
{
"code": 200,
"message": "No matching rules",
"config": "github-auto-deploy"
}
Authentication Failed (401)
{
"code": 401,
"message": "Authentication failed"
}
Config Not Found (404)
{
"code": 404,
"message": "Config 'not-exist' not found"
}
Execution Blocked (409)
When policy: "block" and a task is still running:
{
"code": 409,
"message": "Task 'github-auto-deploy/push-to-main' is running, please try again later"
}
Cooldown Active (429)
When policy: "cooldown" and the cooldown window is active:
{
"code": 429,
"message": "Task 'github-auto-deploy/push-to-main' is in cooldown, retry in 120 seconds"
}
Base Route Disabled (400)
When allow_all: false and a request is sent to /webhook:
{
"code": 400,
"message": "Base route iteration is disabled, please specify target: /webhook/{name}"
}
Relay Registry — Register (200)
{
"status": "registered",
"targets_count": 3
}
Relay Registry — Unregister (200)
{
"status": "unregistered",
"targets_count": 2
}
Relay Registry — List Targets (200)
{
"targets": [
{
"url": "http://10.0.0.5:9000/webhook/deploy-app",
"tags": ["web", "prod"],
"ttl": 120,
"last_seen": "2026-06-11T10:00:00Z",
"expires_at": "2026-06-11T10:02:00Z"
}
],
"total": 1
}
Relay Registry — Errors
| Code | Meaning |
|---|---|
401 | Missing or invalid Bearer token |
405 | Method not allowed |
429 | Registry is full (max_registry_entries reached) |
503 | Relay registry not enabled (no relay_registry_token configured) |
4. Common Scenarios
Scenario 1: GitHub Push Auto-Deploy
name: "github-deploy"
auth:
hmac:
header: "X-Hub-Signature-256"
secret: "your-github-webhook-secret"
execution:
policy: "block"
rules:
- name: "deploy-main"
filters:
- type: "header"
key: "X-GitHub-Event"
operator: "eq"
value: "push"
- type: "body"
key: "ref"
operator: "eq"
value: "refs/heads/main"
actions:
- type: "command"
cmd: "cd /var/www/app && git pull origin {{.body.ref}} && npm run build"
timeout: 120
Webhook URL: POST /webhook/github-deploy
Scenario 2: GitLab CI Pipeline Complete Notification
name: "gitlab-notify"
auth:
token:
source: "header"
key: "X-Gitlab-Token"
value: "your-gitlab-token"
execution:
policy: "always"
rules:
- name: "pipeline-success"
filters:
- type: "header"
key: "X-Gitlab-Event"
operator: "eq"
value: "Pipeline Hook"
- type: "body"
key: "object_attributes.status"
operator: "eq"
value: "success"
actions:
- type: "command"
cmd: "curl -s https://slack.com/api/chat.postMessage -d 'text=Pipeline succeeded!'"
timeout: 10
Scenario 3: Rate-Limited Deployment
name: "rate-limited-deploy"
execution:
policy: "cooldown"
cooldown_seconds: 600 # max once per 10 minutes
rules:
- name: "deploy"
filters:
- type: "header"
key: "X-Event"
operator: "eq"
value: "deploy"
actions:
- type: "script"
path: "/opt/scripts/deploy.sh"
args: ["production"]
timeout: 300
isolate: true
Scenario 4: Multiple Environments in One File
name: "multi-env-deploy"
execution:
policy: "block"
rules:
- name: "deploy-staging"
filters:
- type: "body"
key: "ref"
operator: "eq"
value: "refs/heads/develop"
actions:
- type: "command"
cmd: "deploy.sh staging"
timeout: 120
- name: "deploy-production"
execution:
policy: "cooldown"
cooldown_seconds: 1800 # 30 min cooldown for prod
filters:
- type: "body"
key: "ref"
operator: "eq"
value: "refs/heads/main"
actions:
- type: "command"
cmd: "deploy.sh production"
timeout: 300
Scenario 5: Force Targeted Routes Only
Disable base route iteration to require explicit targeting:
# config.yaml
server:
port: 9000
route: "/webhook"
allow_all: false # must use /webhook/{filename}
All requests to /webhook return 400. Clients must use:
POST /webhook/frontend
POST /webhook/backend
POST /webhook/api
Scenario 6: Multi-Server Relay Deployment
Forward webhooks from a central HookRun to multiple downstream instances using static targets and dynamic tag matching:
# Upstream (main HookRun) — hooks/deploy-app.yaml
name: "deploy-app"
execution:
policy: "block"
rules:
- name: "relay-to-servers"
filters:
- type: "body"
key: "ref"
operator: "regex"
value: "refs/heads/main"
actions:
- type: "relay"
relay:
targets:
- url: "http://10.0.0.2:9000/webhook/deploy-app" # static target
token: "relay-secret-B"
- tag: "prod" # dynamic: all registered targets with "prod" tag
forward_headers:
- "X-GitHub-Event"
timeout: 30
max_relay_hops: 3
# Upstream config.yaml — enable relay registry for dynamic discovery
server:
port: 9000
relay_registry_token: "your-registry-secret"
max_relay_ttl: 300
max_registry_entries: 100
Downstream instances auto-register on startup and maintain heartbeat:
# Downstream (10.0.0.2) config.yaml
server:
port: 9000
relay_client:
upstream: "http://main-hookrun:9000"
url: "http://10.0.0.2:9000/webhook/deploy-app"
token: "relay-secret-B"
registry_token: "your-registry-secret"
tags: ["prod"]
ttl: 120
Scenario 7: Self-Registering Relay Client
A downstream HookRun instance auto-registers with an upstream on startup and sends periodic heartbeats. No manual registration needed:
# Downstream config.yaml
server:
port: 9000
relay_client:
upstream: "http://main-hookrun:9000" # upstream URL (required)
url: "http://10.0.0.5:9000/webhook/deploy-app" # local reachable URL
token: "my-auth-token" # auth token for relay forwarding
registry_token: "your-registry-secret" # registry API Bearer token
tags: ["web", "prod"] # tags for target matching
ttl: 120 # TTL in seconds
webhook_path: "/webhook/deploy-app" # used for URL auto-detection
- On startup, the client sends
POST /api/relay/registerto the upstream - Heartbeat is sent every
ttl / 3seconds to keep the registration alive - On graceful shutdown, the client sends
DELETE /api/relay/registerto unregister - If
urlis omitted, it is auto-detected from local IP + port +webhook_path
5. Logging
Daily Mode (default)
- Logs are written to
{log.path}/hookrun-YYYY-MM-DD.log - A new file is created each day
- Files older than
retention_daysare automatically deleted on startup
Single Mode
- Logs are written to
{log.path}/hookrun.log - One fixed file; optional size-based rotation (
max_size_mb) - Suitable for container environments or external log management
Rule-Level Log
- Each rule config can specify
log.pathfor an independent log file - Logs are dual-written to both global and rule-specific log files
Each webhook request and execution result is logged with timestamp.
6. Configuration Validation Best Practices
-
Always validate before starting:
hookrun validate && hookrun start -
Validate after editing:
# Edit a YAML file vim hooks/my-app.yaml # Validate without reloading hookrun validate # If valid, reload hookrun reload -
Use foreground mode for debugging:
hookrun start -fThis shows all logs in the terminal for immediate feedback.