Configuration Reference

June 24, 2026 · View on GitHub

中文

HookRun uses two levels of YAML configuration:

  1. Global Config (config.yaml) — Server, logging, and directory settings
  2. Rule Config (hooks/*.yaml) — Authentication, filters, and actions per scenario

1. Global Config — config.yaml

FieldTypeDefaultDescription
server.portint9000HTTP listen port (1–65535)
server.routestring/webhookBase webhook endpoint path
server.allow_allboolfalseAllow base route (/webhook) to iterate all config files
server.max_body_size_mbint10Max request body size in MB. 0 = unlimited
server.relay_registry_tokenstring""Relay registry API auth token. Non-empty enables registry
server.max_relay_ttlint0 (unlimited)Max TTL cap for registered targets (seconds)
server.max_registry_entriesint100Max number of registered targets
log.modestringdailyLog mode: "daily" (rotate by day) or "single" (one file)
log.pathstring./logsLog directory (daily) or base path (single)
log.retention_daysint30Days to retain daily log files
log.max_size_mbint0 (unlimited)Max file size in MB before rotation (single mode only)
config_dirstring./hooksDirectory containing rule YAML files

Example

server:
  port: 9000
  route: "/webhook"
  allow_all: false

log:
  mode: "daily"                # "daily" (default) | "single"
  path: "./logs"
  retention_days: 30           # only for daily mode
  # max_size_mb: 0             # only for single mode, 0 = unlimited (default)

config_dir: "./hooks"

server.allow_all

Controls whether the base route /webhook iterates through all YAML config files.

  • true — Requests to /webhook iterate all config files, stopping at the first matching rule
  • false (default) — Requests to /webhook return 400; clients must use /webhook/{filename}

server.max_body_size_mb

Limits the maximum size of incoming request bodies to prevent DoS attacks.

  • 10 (default) — Request bodies larger than 10 MB are rejected with HTTP 413
  • 0 — No limit (use with caution)
  • Any positive integer — Custom limit in MB

server.relay_registry_token — Dynamic Target Discovery

Enables the relay registry API for dynamic target discovery. When set, downstream HookRun instances can auto-register with this instance.

# Upstream (main HookRun)
server:
  port: 9000
  relay_registry_token: "your-registry-secret"   # API auth for registration
  max_relay_ttl: 300                               # cap TTL at 5 min
  max_registry_entries: 100                        # max pool capacity

Relay API endpoints:

MethodPathAuthFunction
GET/api/relay/statusNoShow relay role and status (always available)
POST/api/relay/registerBearer TokenRegister or refresh a target *
DELETE/api/relay/registerBearer TokenUnregister a target *
GET/api/relay/targetsBearer TokenList all registered targets *

* Only available when relay_registry_token is set.

Registration body (POST /api/relay/register):

{
  "url": "http://10.0.0.5:9000/webhook/deploy-app",
  "token": "downstream-auth-token",
  "tags": ["web", "prod"],
  "ttl": 120
}
FieldTypeRequiredDescription
urlstringYesDownstream webhook URL
tokenstringNoDownstream auth token (used when upstream relays events to this target)
tagsarrayNoTags for target matching
ttlintNoTTL in seconds (may be capped by max_relay_ttl)

Authentication example (Bearer Token = upstream's relay_registry_token):

curl -X POST http://upstream:9000/api/relay/register \
  -H "Authorization: Bearer your-registry-secret" \
  -H "Content-Type: application/json" \
  -d '{"url":"http://10.0.0.5:9000/webhook","token":"downstream-auth-token","tags":["prod"],"ttl":120}'

relay_client — Auto-Registration (Downstream)

Configures a downstream HookRun instance to auto-register with an upstream on startup and maintain heartbeat:

# Downstream HookRun 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 URL (auto-detected if omitted)
  token: "my-auth-token"                         # downstream auth token
  registry_token: "your-registry-secret"         # registry API token
  tags: ["web", "prod"]                          # tags for target matching
  ttl: 120                                       # TTL in seconds
  webhook_path: "/webhook/deploy-app"            # used for URL auto-detection
FieldTypeRequiredDescription
upstreamstringYesUpstream HookRun URL
urlstringNoLocal reachable URL (auto-detected from IP + port + webhook_path if omitted)
tokenstringNoAuth token included when upstream relays to this instance
registry_tokenstringNoBearer token for registry API auth
tagsarrayYesTags for dynamic target matching
ttlintNoSuggested TTL in seconds (may be capped by upstream max_relay_ttl)
webhook_pathstringNoWebhook path for URL auto-detection (default: /webhook)

log.mode

Controls how log files are generated.

ModeBehaviorFile Naming
daily (default)One file per day, auto-cleanuphookrun-2026-06-11.log
singleOne fixed file, optional size rotationhookrun.log

Daily mode is suitable for long-running services with retention_days auto-cleanup.

Single mode is suitable for container environments (Docker/Kubernetes) or when external tools handle rotation. max_size_mb: 0 means no size rotation (unlimited).

# Container-friendly: single file, no rotation
log:
  mode: "single"
  path: "./logs"
  # max_size_mb: 0  (unlimited, let Docker handle rotation)

2. Rule Config — hooks/*.yaml

Each YAML file in config_dir defines a rule set. The filename (without .yaml) is used as the routing key for /webhook/{filename}.

Top-Level Fields

FieldTypeRequiredDescription
namestringYesRule set name, used in logs and responses
authobjectNoAuthentication settings (AND relationship)
executionobjectNoFile-level execution policy
filtersarrayNoFile-level global filters (AND with rule-level)
logobjectNoRule-level independent log file (dual-write with global)
rulesarrayYesList of rules (at least one required)

2.1 auth — Authentication

All configured checks use AND relationship — every check must pass.

FieldTypeRequiredDescription
auth.token.sourcestringIf token set"header" or "query" — where to read the token
auth.token.keystringIf token setHeader name or query parameter name
auth.token.valuestringIf token setExpected token value
auth.hmac.headerstringIf hmac setSignature header name, e.g. X-Hub-Signature-256
auth.hmac.secretstringIf hmac setHMAC secret key (from webhook provider settings)
auth.hmac.algorithmstringNo"sha256" (default) | "sha1" | "sha512"
auth.hmac.prefixstringNoSignature prefix, e.g. "sha256=" (auto-derived from algorithm if empty)
auth.ip_whitelistarrayNoList of allowed IPs (supports CIDR)

Example

auth:
  token:
    source: "header"
    key: "X-Webhook-Token"
    value: "secret123"
  hmac:
    header: "X-Hub-Signature-256"
    secret: "your-webhook-secret"
    algorithm: "sha256"
  ip_whitelist:
    - "192.168.1.100"
    - "10.0.0.0/24"

If only token is set, only token validation is required. If only hmac is set, only HMAC signature validation is required. If only ip_whitelist is set, only IP validation is required. If multiple are set, all must pass (AND relationship).

HMAC Signature Verification

HMAC verification computes a signature over the raw request body using the configured secret and algorithm, then compares it against the signature provided in the request header. This is the standard verification method used by GitHub, GitLab, Bitbucket, and other platforms.

PlatformHeaderAlgorithmPrefix
GitHubX-Hub-Signature-256sha256sha256=
GitLabX-Gitlab-Token— (plain token, use auth.token instead)
BitbucketX-Hub-Signaturesha256sha256=

GitHub example:

auth:
  hmac:
    header: "X-Hub-Signature-256"
    secret: "your-github-webhook-secret"
    # algorithm defaults to "sha256", prefix auto-derived as "sha256="

Custom prefix example (for platforms with non-standard formats):

auth:
  hmac:
    header: "X-Signature"
    secret: "my-secret"
    algorithm: "sha512"
    prefix: "sha512="

2.2 execution — Execution Policy

Can be set at file-level (applies to all rules) or rule-level (overrides file-level).

FieldTypeRequiredDescription
policystringYes"block" | "always" | "cooldown"
cooldown_secondsintIf cooldownCooldown interval in seconds (must be > 0)

Policy Types

PolicyBehaviorHTTP ResponseUse Case
blockReject if previous execution is still running409Deploy, build, long tasks
alwaysAlways spawn a new execution200Stateless notifications, logging
cooldownReject if within cooldown window429Rate-limited scenarios

Inheritance

# File-level default
execution:
  policy: "block"

rules:
  - name: "deploy"
    # Inherits file-level: block
    ...

  - name: "notify"
    execution:
      policy: "always"    # Overrides: always execute
    ...

Priority: Rule-level > File-level > Default (block)


2.3 filters — File-Level Filters

File-level filters act as global constraints applied to ALL rules in the config. They use AND logic with rule-level filters: both must match for a rule to execute.

If file-level filters fail, all rules are skipped (short-circuit) — no individual rule is evaluated.

A rule with no filters (at either level) acts as a catch-all — it matches every request that reaches it. In a first-match-wins chain, place catch-all rules last as fallbacks.

# File-level: common constraint for all rules
filters:
  - type: "header"
    key: "X-GitHub-Event"
    operator: "eq"
    value: "push"

rules:
  - name: "deploy-main"
    filters:                         # AND with file-level
      - type: "body"
        key: "ref"
        operator: "eq"
        value: "refs/heads/main"
    actions: [...]

  - name: "notify-all"
    # No rule-level filters — matches whenever file-level filters pass
    actions: [...]

Filter field definitions, types, and operators are the same as rule-level filters (see section 2.4).


2.3.5 log — Rule-Level Log

Each rule config file can specify an independent log file. Logs are dual-written to both the global log and the rule-specific log (similar to nginx access_log).

FieldTypeRequiredDescription
pathstringYesFull log file path (e.g. ./logs/deploy.log)

File naming follows the global log.mode:

Modelog.pathGenerated File
daily./logs/deploy.log./logs/deploy-2026-06-11.log
single./logs/deploy.log./logs/deploy.log
log:
  path: "./logs/deploy.log"

Rule-level loggers inherit retention_days and max_size_mb from global settings.


2.4 rules — Rule List

Each rule has a name, filters, and actions.

FieldTypeRequiredDescription
namestringYesRule name, used in logs and responses
executionobjectNoRule-level execution policy (overrides file-level)
filtersarrayNoMatching conditions (AND relationship; empty = catch-all)
actionsarrayYesCommands/scripts to execute (at least one)

2.5 filters — Rule-Level Matching Conditions

Multiple filters within a rule use AND relationship — all must match. Combined with file-level filters (if any) via AND.

FieldTypeRequiredDescription
typestringYes"header" | "query" | "body"
keystringYesField name to check (body supports JSON path)
operatorstringYes"eq" | "ne" | "contains" | "regex"
valuestringYesExpected value to match against

Filter Types

TypeSourceExample
headerHTTP request headersX-GitHub-Event
queryURL query parameters?event=push
bodyJSON request bodyref, commits[0].message

Operators

OperatorDescriptionExample
eqExact matchref eq refs/heads/main
neNot equalstatus ne closed
containsSubstring matchmessage contains deploy
regexRegular expressionref regex refs/tags/v.*

JSON Path (body type)

Supports dot notation and array indexing:

- type: "body"
  key: "ref"                    # top-level field
- type: "body"
  key: "commits[0].message"     # nested with array index
- type: "body"
  key: "repository.owner.name"  # deeply nested

2.6 actions — Actions to Execute

Actions execute sequentially in the order defined.

FieldTypeRequiredDefaultDescription
typestringYes"command", "script", "webhook", or "relay"
cmdstringIf commandShell command to execute (supports template variables)
pathstringIf scriptScript file path (supports template variables)
argsarrayNo[]Arguments for script (supports template variables)
pass_argsarrayNo[]Extract from request and append as arguments
env_fromarrayNo[]Extract from request and inject as environment variables
timeoutintNo0 (no limit)Timeout in seconds
isolateboolNofalseRun in isolated subprocess
continue_on_errorboolNofalseContinue to next action if this one fails

Action Types

TypeDescriptionRequired Fields
commandInline shell commandcmd
scriptExternal script filepath
webhookHTTP request to external URLurl
relayForward request to multiple HookRun instancesrelay.targets

Platform Behavior

PlatformShellFlag
Linux/macOSsh-c
Windowscmd/c

Environment Variable

All executed commands receive HOOKRUN=1 in their environment.

Webhook Action

The webhook type sends an HTTP request to an external URL — useful for notifying Slack, DingTalk, Feishu, or cascading HookRun instances.

FieldTypeRequiredDefaultDescription
urlstringYesTarget URL (supports template variables)
methodstringNoPOSTHTTP method: POST, PUT, PATCH, GET
headersmapNo{}Custom headers (supports template variables)
forward_headersarrayNo[]Whitelist of original request headers to forward
bodystringNo""Request body template (supports {{.raw_body}})
timeoutintNo30Timeout in seconds

Auto Headers — Every webhook request includes:

HeaderValue
X-HookRun-SourceHookRun/v<version>
X-HookRun-ConfigConfig file name (e.g. deploy-app)
X-HookRun-RuleRule name (e.g. on-push)

Header merge priority: Auto headers → forward_headersheaders (custom overrides all).

{{.raw_body}} template — Injects the entire original request body as raw JSON. Only available in webhook body field (not in command/script actions to prevent shell injection).

Important: The body field must be a YAML string. Wrap JSON in quotes ('...') or use block scalar (|). Template variables like {{.raw_body}} must include the {{ }} delimiters — writing .raw_body alone will be treated as literal text.

# ✓ Correct — body is a string with template syntax
body: '{"event":"push","payload":{{.raw_body}}}'

# ✓ Correct — block scalar
body: |
  {"text": "Deploy: {{.body.ref}}", "payload": {{.raw_body}}}

# ✗ Wrong — YAML mapping (causes parse error)
body:
  event: "push"
  payload: .raw_body
actions:
  # Forward entire body to another service
  - type: "webhook"
    url: "https://another-hookrun.example.com/webhook/deploy"
    body: "{{.raw_body}}"

  # Wrap original body with extra fields
  - type: "webhook"
    url: "https://hooks.slack.com/services/xxx"
    body: |
      {"text": "Deploy: {{.body.ref}}", "payload": {{.raw_body}}}

  # Forward specific headers + custom auth
  - type: "webhook"
    url: "https://api.example.com/notify"
    method: "PUT"
    forward_headers:
      - "X-GitHub-Event"
      - "X-Request-Id"
    headers:
      Authorization: "Bearer {{.body.token}}"
    body: '{"event": "{{.header.x-event}}"}'

Retry

Actions can be configured to retry on failure with exponential backoff:

FieldTypeRequiredDefaultDescription
max_attemptsintNo1Total attempts including first (must >= 1)
interval_secondsintNo0Base interval in seconds between retries (must >= 0)
actions:
  - type: "command"
    cmd: "deploy.sh"
    retry:
      max_attempts: 3           # try up to 3 times (1 initial + 2 retries)
      interval_seconds: 5       # base interval: 5s

Exponential backoff — Retry intervals grow automatically: interval × 2^(attempt-1) with ±25% random jitter, capped at 5 minutes.

Example with interval_seconds: 5:

  • 1st retry: ~5s
  • 2nd retry: ~10s
  • 3rd retry: ~20s

interval_seconds: 0 means immediate retry with no wait. Retry applies to all action types (command, script, webhook). All retries are exhausted before continue_on_error is evaluated.

Template Variables

Commands, script paths, and script arguments support template variables that are resolved from the incoming request at runtime:

TemplateSourceExample
{{.raw_body}}Raw request bodyFull JSON body as-is
{{.body.<path>}}JSON request body{{.body.ref}}, {{.body.repository.owner.name}}
{{.header.<name>}}HTTP request header{{.header.X-GitHub-Event}}
{{.query.<name>}}URL query parameter{{.query.token}}

Body paths support dot notation and array indexing (same as filter body type).

actions:
  - type: "command"
    cmd: "git checkout {{.body.ref}} && echo 'Event: {{.header.X-GitHub-Event}}'"

If a template variable cannot be resolved, it is replaced with an empty string and a warning is logged.

pass_args — Extract and Append Parameters

pass_args extracts values from the request and appends them as trailing arguments to the command or script. This is useful for passing dynamic data without embedding template variables in the command string.

FieldTypeRequiredDescription
sourcestringYes"header" | "query" | "body"
keystringYesField name or JSON path for body
actions:
  - type: "command"
    cmd: "echo 'Deploying:'"
    pass_args:
      - source: "body"
        key: "ref"
      - source: "header"
        key: "X-GitHub-Event"

When the webhook receives {"ref": "refs/heads/main"} with header X-GitHub-Event: push, the executed command becomes:

echo 'Deploying:' refs/heads/main push

env_from — Inject Environment Variables

env_from extracts values from the request and injects them as environment variables into the command/script subprocess. All variable names are automatically prefixed with HOOKRUN_ to prevent conflicts with system variables.

FieldTypeRequiredDescription
sourcestringYes"header" | "query" | "body"
keystringYesField name or JSON path for body
envstringYesEnv var suffix (auto-prefixed with HOOKRUN_)

Default environment variables (always available in command/script):

VariableDescription
HOOKRUN_RAW_BODYRaw request body as-is
HOOKRUN_TRIGGER_IPIP address of the request sender
actions:
  - type: "script"
    path: "./scripts/deploy.sh"
    env_from:
      - source: "body"
        key: "ref"
        env: "GIT_REF"            # → $HOOKRUN_GIT_REF
      - source: "header"
        key: "X-GitHub-Event"
        env: "GITHUB_EVENT"       # → $HOOKRUN_GITHUB_EVENT

In your script:

#!/bin/bash
echo "Event: $HOOKRUN_GITHUB_EVENT"    # → push
echo "Ref: $HOOKRUN_GIT_REF"           # → refs/heads/main
echo "Full body: $HOOKRUN_RAW_BODY"    # → {"ref":"refs/heads/main",...}
echo "From: $HOOKRUN_TRIGGER_IP"       # → 192.30.252.0

Note: If env already starts with HOOKRUN_, the prefix is not duplicated.

Relay Action — Instance-to-Instance Forwarding

The relay type forwards the original webhook request body to multiple HookRun instances. Useful for multi-server deployments, multi-environment fan-out, and network isolation scenarios.

FieldTypeRequiredDefaultDescription
targetsarrayYesList of targets (each with url and optional token)
forward_headersarrayNo[]Whitelist of original headers to forward
max_relay_hopsintNo3Anti-loop limit (0 = use default 3)
timeoutintNo30Per-target timeout in seconds

Target config:

FieldTypeRequiredDescription
urlstringOne of url/tagStatic target HookRun instance URL
tokenstringNoAuth token for downstream
tagstringOne of url/tagDynamic target tag — matches all registered instances with this tag
actions:
  - type: "relay"
    relay:
      targets:
        - url: "http://10.0.0.2:9000/webhook/deploy-app"   # static target
          token: "relay-secret-B"
        - url: "http://10.0.0.3:9000/webhook/deploy-app"
          token: "relay-secret-C"
        - tag: "prod"                                      # dynamic: all registered instances tagged "prod"
      forward_headers:
        - "X-GitHub-Event"
      timeout: 30
      max_relay_hops: 3

Auto Headers — each relay request automatically includes:

HeaderValue
X-HookRun-Relaytrue
X-HookRun-Relay-FromSender IP
X-HookRun-Request-IDUnique request identifier
X-HookRun-Relay-HopsCurrent hop count + 1
X-HookRun-Relay-TokenTarget's configured token
X-HookRun-SourceHookRun/v<version>

Anti-loop: X-HookRun-Relay-Hops increments by 1 at each hop. When hops reach max_relay_hops, relay is rejected to prevent infinite loops.

Idempotent Deduplication: Downstream instances can configure a dedup window to prevent duplicate execution from retries:

# Downstream HookRun instance config
name: "deploy-app"
auth:
  token:
    source: "header"
    key: "X-HookRun-Relay-Token"
    value: "relay-secret-B"
deduplicate:
  enabled: true
  window_seconds: 600    # same Request-ID only triggers once within 10 minutes

rules:
  - name: "deploy"
    actions:
      - type: "command"
        cmd: "cd /var/www/app && git pull && npm run build"
FieldTypeRequiredDefaultDescription
enabledboolNofalseEnable deduplication
window_secondsintRequired when enabled300Dedup window in seconds

Example

actions:
  - type: "command"
    cmd: "cd /var/www/app && git pull"
    timeout: 60
  - type: "command"
    cmd: "npm install --production"
    timeout: 120
    continue_on_error: true
  - type: "script"
    path: "./scripts/deploy.sh"
    args: ["production", "v2.1"]
    timeout: 300
    isolate: true

3. Complete Example

name: "github-auto-deploy"

auth:
  hmac:
    header: "X-Hub-Signature-256"
    secret: "your-github-webhook-secret"
  ip_whitelist:
    - "192.30.252.0/22"

execution:
  policy: "block"

# File-level filters: global constraints for ALL rules
filters:
  - type: "header"
    key: "X-GitHub-Event"
    operator: "eq"
    value: "push"

rules:
  - name: "push-to-main"
    filters:   # AND with file-level
      - type: "body"
        key: "ref"
        operator: "eq"
        value: "refs/heads/main"
    actions:
      - type: "command"
        cmd: "cd /var/www/app && git pull origin {{.body.ref}}"
        timeout: 30
      - type: "command"
        cmd: "cd /var/www/app && npm install --production && npm run build"
        timeout: 120
      - type: "script"
        path: "./scripts/restart.sh"
        args: ["{{.body.ref}}"]
        timeout: 60

  - name: "tag-release"
    execution:
      policy: "always"
    filters:
      - type: "body"
        key: "ref"
        operator: "regex"
        value: "refs/tags/v.*"
    actions:
      - type: "command"
        cmd: "echo 'Release tag:'"
        pass_args:
          - source: "body"
            key: "ref"
        timeout: 10

# Rule-level independent log (dual-write: global + this file)
log:
  path: "./logs/github-auto-deploy.log"

4. Secret References

All string fields in YAML configuration support ${env:} and ${file:} interpolation to avoid storing secrets in plain text.

${env:VAR_NAME}

Reads the value from an environment variable.

auth:
  token:
    source: "header"
    key: "X-Webhook-Token"
    value: "${env:WEBHOOK_TOKEN}"
  hmac:
    header: "X-Hub-Signature-256"
    secret: "${env:GITHUB_WEBHOOK_SECRET}"

If the environment variable is not set, HookRun will fail to start with a clear error message.

${file:/path/to/secret}

Reads the value from a file. Trailing whitespace and newlines are automatically trimmed.

auth:
  token:
    source: "header"
    key: "X-Webhook-Token"
    value: "${file:/run/secrets/webhook_token}"

This is compatible with Docker Secrets, Kubernetes Secrets (mounted as files), and systemd credentials.

Notes

  • Interpolation is applied to all string fields in both config.yaml and hooks/*.yaml
  • Multiple references can be used in the same file
  • If a referenced env var or file is missing, HookRun reports an error at load time (fail-safe)