Configuration Reference¶
Hookaido uses a Caddyfile-inspired DSL. The config file (typically Hookaidofile) is the source of truth for all routing, authentication, queue behavior, and observability settings.
File Structure¶
A Hookaidofile consists of global blocks and route blocks, optionally organized by channel type:
# Global blocks
ingress { ... }
pull_api { ... }
admin_api { ... }
queue_limits { ... }
queue_retention { ... }
defaults { ... }
secrets { ... }
observability { ... }
vars { ... }
# Inbound routes (implicit — bare top-level routes receive ingress traffic)
/webhooks/github {
auth hmac env:HOOKAIDO_SECRET
pull { path /pull/github }
}
# Explicit inbound wrapper (optional, equivalent to bare routes)
inbound {
/webhooks/stripe { ... }
}
# Outbound routes (API → queue → push, no ingress traffic)
outbound /jobs/deploy {
deliver "https://ci.internal/deploy" { timeout 10s }
}
# Internal routes (job queues, no ingress traffic)
internal {
/jobs/report {
pull { path /pull/reports }
}
}
Route paths can be quoted or unquoted but must start with /.
Channel Types¶
Routes are organized into three channel types that control which directives are allowed:
| Channel | Ingress traffic | auth/match/rate_limit |
pull |
deliver |
publish |
queue { backend } |
Labels |
|---|---|---|---|---|---|---|---|
inbound (default) |
yes | allowed | allowed | allowed | allowed | allowed | allowed |
outbound |
no | forbidden | forbidden | required | allowed | allowed | allowed |
internal |
no | forbidden | required | forbidden | allowed | allowed | allowed |
inbound (default)¶
Bare top-level routes are implicit inbound — they receive ingress HTTP traffic and support the full set of per-route directives. The inbound { } wrapper is optional syntactic sugar.
outbound¶
For API-to-queue-to-push flows where your application publishes events via the Admin API or MCP, and Hookaido delivers them to external targets. No ingress listener serves these routes.
Compile constraints:
deliveris required (at least one target).auth,match,rate_limit, andpullare forbidden.
internal¶
For internal job queues consumed by your workers via the Pull API. No ingress listener serves these routes, and no push delivery is configured.
Compile constraints:
pullis required.auth,match,rate_limit, anddeliverare forbidden.
Wrapper Form¶
All three channel types support both single-route shorthand and multi-route wrapper form:
# Single route
outbound /jobs/deploy {
deliver "https://ci.internal/deploy" { timeout 10s }
}
# Multiple routes
internal {
/jobs/report { pull { path /pull/reports } }
/jobs/cleanup { pull { path /pull/cleanup } }
}
Global Blocks¶
ingress¶
Controls the ingress HTTP listener.
ingress {
listen :8080
rate_limit {
rps 100
burst 200 # optional; defaults to ceil(rps)
}
tls {
cert_file /path/to/cert.pem
key_file /path/to/key.pem
client_ca /path/to/ca.pem # optional, enables mTLS
client_auth require_and_verify # optional
}
}
| Directive | Default | Description |
|---|---|---|
listen |
:8080 |
Bind address |
rate_limit |
— | Global ingress rate limit (token-bucket) |
tls |
— | TLS and optional mTLS configuration |
pull_api¶
Controls the Pull API listener for consumer workers.
pull_api {
listen :9443
prefix /pull # optional URL prefix
auth token env:HOOKAIDO_PULL_TOKEN
max_batch 100 # max items per dequeue (default 100)
default_lease_ttl 30s # default lease duration (default 30s)
max_lease_ttl 5m # optional upper bound for lease TTL
default_max_wait 0 # default long-poll wait (default 0 = no wait)
max_wait 30s # optional upper bound for long-poll wait
tls { ... }
}
| Directive | Default | Description |
|---|---|---|
listen |
:9443 |
Bind address |
prefix |
— | URL path prefix for all pull endpoints |
auth token |
required | Bearer token allowlist (env:/file:/vault:/raw: ref) |
max_batch |
100 |
Max items per dequeue request |
default_lease_ttl |
30s |
Lease TTL when client omits it |
max_lease_ttl |
off | Optional upper cap for effective lease TTL |
default_max_wait |
0 |
Long-poll wait when client omits it |
max_wait |
off | Optional upper cap for long-poll wait |
tls |
— | TLS and optional mTLS configuration |
Pull API auth is required when pull routes are present. Deliver-only configs can omit it entirely — the Pull API server is skipped in that case.
admin_api¶
Controls the Admin API listener for operator tooling.
admin_api {
listen 127.0.0.1:2019
prefix /admin # optional URL prefix
auth token env:HOOKAIDO_ADMIN_TOKEN # optional
tls { ... }
}
| Directive | Default | Description |
|---|---|---|
listen |
127.0.0.1:2019 |
Bind address |
prefix |
— | URL path prefix |
auth token |
— | Optional bearer token allowlist |
tls |
— | TLS and optional mTLS configuration |
Shared listener: If
pull_api.listen == admin_api.listen, both must define non-overlappingprefixvalues (e.g.,/pulland/admin).
queue_limits¶
queue_limits {
max_depth 10000 # max queued items before backpressure
drop_policy reject # "reject" (429) or "drop_oldest"
}
queue_retention¶
queue_retention {
max_age 7d # prune queued items older than this
prune_interval 5m # how often the pruner runs
}
Set max_age off (or 0) to disable retention.
delivered_retention¶
Pruning uses the same queue_retention.prune_interval cadence. Set max_age off to disable.
dlq_retention¶
dlq_retention {
max_age 30d # prune dead items older than this
max_depth 10000 # cap the dead-letter set
}
secrets¶
Define named secrets with validity windows for key rotation:
secrets {
secret "S1" {
value env:MY_SECRET_V1
valid_from "2026-01-01T00:00:00Z"
valid_until "2026-07-01T00:00:00Z"
}
secret "S2" {
value env:MY_SECRET_V2
valid_from "2026-06-01T00:00:00Z"
}
}
valid_fromis inclusive,valid_untilis exclusive.- Omit
valid_untilfor "no expiry". - Referenced via
secret_ref "S1"in auth and signing blocks. valueacceptsenv:,file:,vault:, andraw:refs.
vars¶
Reusable values with nested expansion:
Referenced as {vars.NAME} in any value position. Cycles are detected at compile time.
defaults¶
Global defaults for body limits, delivery, egress policy, and publish policy:
defaults {
max_body 2mb
max_headers 64kb
egress {
allow "*.internal.example.com"
deny "169.254.0.0/16"
https_only on
redirects off
dns_rebind_protection on
}
deliver {
retry exponential max 8 base 2s cap 2m jitter 0.2
timeout 10s
concurrency 20
}
publish_policy {
direct on
managed on
allow_pull_routes on
allow_deliver_routes on
require_actor off
require_request_id off
fail_closed off
actor_allow "ci-bot"
actor_prefix "deploy-"
}
trend_signals {
window 15m
expected_capture_interval 1m
stale_grace_factor 3
sustained_growth_consecutive 3
sustained_growth_min_samples 5
sustained_growth_min_delta 10
recent_surge_min_total 20
recent_surge_min_delta 10
recent_surge_percent 50
dead_share_high_min_total 10
dead_share_high_percent 20
queued_pressure_min_total 20
queued_pressure_percent 75
queued_pressure_leased_multiplier 2
}
}
observability¶
See Observability for full reference.
observability {
access_log { enabled on; output stderr; format json }
runtime_log { level info; output stderr; format json }
metrics { listen ":9900"; prefix "/metrics" }
tracing { enabled on; collector "https://otel.example.com/v1/traces" }
}
Route Blocks¶
Each route block defines a webhook endpoint path and its processing pipeline:
/webhooks/github {
# Optional management labels
application "github"
endpoint_name "push-events"
# Optional matchers (ANDed with path)
match {
method POST
host "hooks.example.com"
header "X-GitHub-Event" "push"
header_exists "X-GitHub-Delivery"
query "env" "production"
query_exists "token"
remote_ip "203.0.113.0/24"
}
# Optional rate limit override
rate_limit { rps 50 }
# Authentication (pick one)
auth hmac env:HOOKAIDO_GITHUB_SECRET
# auth basic "user" "pass"
# auth forward "https://auth.example/check"
# Route-level publish control
publish {
enabled on # default; set "off" to block manual publish
direct on # controls global direct publish path
managed on # controls endpoint-scoped managed publish path
}
# Queue backend
queue { backend sqlite } # or "memory"
# Mode: pull OR deliver (not both)
pull { path /pull/github }
# Or push mode:
# deliver "https://ci.internal/build" {
# retry exponential max 5 base 1s cap 30s jitter 0.1
# timeout 10s
# sign hmac env:DELIVER_SECRET
# }
}
Routing Semantics¶
- Evaluation is top-down, first match wins.
- Path match uses URL path only (query ignored).
"/path"matches/pathand/path/...(segment boundary), but not/path-foo.- Route paths must be unique (path is the queue key).
- Match criteria within a route are ANDed.
Named Matchers¶
Define reusable matchers and reference them:
@github-push {
method POST
header "X-GitHub-Event" "push"
}
/webhooks/github {
match @github-push
pull { path /pull/github }
}
Authentication Options¶
HMAC (shorthand):
HMAC (block form):
auth hmac {
secret env:HOOKAIDO_SECRET
# or: secret_ref "S1"
signature_header "X-Signature"
timestamp_header "X-Timestamp"
nonce_header "X-Nonce"
tolerance 5m
}
Basic auth:
Forward auth:
auth forward "https://auth.example/check"
# or with options:
auth forward "https://auth.example/check" {
timeout 5s
copy_headers "X-User-ID"
copy_headers "X-Org-ID"
body_limit 64kb
}
auth forwardis mutually exclusive withauth basicandauth hmac.
Deliver Blocks (Push Mode)¶
deliver "https://ci.internal/build" {
retry exponential max 8 base 2s cap 2m jitter 0.2
timeout 10s
# Optional outbound HMAC signing
sign hmac env:DELIVER_SECRET
sign signature_header "X-Hookaido-Signature" # default
sign timestamp_header "X-Hookaido-Timestamp" # default
# Or with secret rotation:
sign hmac secret_ref "S1"
sign hmac secret_ref "S2"
sign secret_selection newest_valid # or oldest_valid
}
Per-route concurrency can override the global default:
Placeholders¶
Hookaido supports four placeholder syntaxes in config values:
| Syntax | Resolved | Description |
|---|---|---|
{$VAR} |
Compile time | Environment variable (Hookaidofile only) |
{$VAR:default} |
Compile time | Environment variable with fallback |
{env.VAR} |
Startup/reload | Environment variable at runtime |
{file.PATH} |
Startup/reload | File content (read failure = config error) |
{vars.NAME} |
Startup/reload | Value from the vars block |
Placeholders resolve within a single value (no cross-token expansion).
Defaults Table¶
| Setting | Default Value |
|---|---|
max_body |
2mb |
max_headers |
64kb |
queue_limits.max_depth |
10000 |
queue_limits.drop_policy |
reject |
queue_retention.max_age |
7d |
queue_retention.prune_interval |
5m |
dlq_retention.max_age |
30d |
dlq_retention.max_depth |
10000 |
deliver.retry |
exponential max 8 base 2s cap 2m jitter 0.2 |
deliver.timeout |
10s |
deliver.concurrency |
20 |
pull_api.max_batch |
100 |
pull_api.default_lease_ttl |
30s |
Config Management¶
# Validate (JSON or text output)
hookaido config validate --config ./Hookaidofile --format json
# Optional strict secret preflight (env/file/vault/raw refs are loaded)
hookaido config validate --config ./Hookaidofile --strict-secrets --format text
# Format (canonical, idempotent, preserves quoting style)
hookaido config fmt --config ./Hookaidofile
Config changes are round-trip safe — config fmt is stable and diff-friendly.
config validate checks secret-reference syntax for token/signing/value refs (env:, file:, vault:, raw:).
Use --strict-secrets when you also want availability/reachability preflight (for example missing env vars, unreadable files, or Vault access failures).
Hot Reload¶
With --watch or SIGHUP, Hookaido reloads the Hookaidofile and applies changes live where safe. If a change requires a restart, the reload is rejected and a config_reloaded_restart_required log is emitted — the previous config stays active.
# File-watch mode (automatic)
hookaido run --config ./Hookaidofile --watch
# Manual signal reload
kill -HUP $(cat ./hookaido.pid)
Live-Reloadable (no restart)¶
| Config area | Notes |
|---|---|
| Route table (add/remove/reorder routes, paths, match rules) | |
Pull endpoint mappings (pull { path ... }) |
|
| Auth settings (HMAC secrets, basic auth, forward auth, tokens) | Per-route and global |
| Rate limits (global + per-route) | |
Management model labels (application, endpoint_name) |
|
Route-level max_body / max_headers |
Per-route overrides only |
Route-level publish / publish.direct / publish.managed |
|
| Trend signals config |
Restart Required¶
If any of these change, Hookaido rejects the reload and requires a process restart:
| Config area | Reason |
|---|---|
Listener addresses (ingress.listen, pull_api.listen, admin_api.listen) |
Socket rebind |
Listener TLS (tls { ... } on any listener) |
TLS config baked at startup |
API prefixes (pull_api.prefix, admin_api.prefix) |
HTTP mux topology |
| Shared listener mode toggle | Server topology |
Pull API limits (max_batch, *_lease_ttl, *_max_wait) |
Set on server struct at startup |
defaults.max_body / defaults.max_headers (global defaults) |
Set on ingress/admin servers at startup |
defaults.publish_policy.* (all publish policy fields) |
Set on admin server at startup |
| Deliver targets, URLs, retry, timeout, concurrency, signing | Dispatcher goroutine topology |
Egress policy (defaults.egress.*) when deliver routes exist |
Baked into dispatcher |
Queue backend type (sqlite ↔ memory) |
No migration path |
| Queue limits / retention / DLQ retention | Set on queue store at startup |
| Observability (log sinks, tracing, metrics) | Exporter/sink initialized once |
| Adding/removing first pull or last deliver route | Creates/destroys server/dispatcher |