Hookway

CLI

For a normal ingress deployment, run hookway. Add hookway-replay only if you need to retain and retry failed deliveries after an early acknowledgement. These are the two terminal programs shipped by Hookway.

CommandWhat it isImage
hookwayCore. It serves webhooks, and it checks configs, prints the config schema, and serves the config builder.hookway
hookway-replayThe optional replay service. It keeps the deliveries core missed after the sender got its 2xx, and retries them when you ask.hookway-replay (Docker Hub: <image>-replay)

Getting help

Each terminal command has a help page with its purpose, flags and examples:

hookway help                  # every command, the environment, exit codes
hookway help validate         # one command: what it does, its flags, examples
hookway validate -h           # the same page
hookway-replay help migrate

Help goes to stdout and exits 0. A mistake exits 2 and prints the error and a pointer to the help on stderr: an unknown command, a flag the command does not have, or an argument it does not take. That last one catches hookway validate config.yaml, which is refused instead of quietly validating $HOOKWAY_CONFIG.

Flags take one dash or two (-config and --config are the same). A value follows a space or = (--config x.yaml, --config=x.yaml), except for on/off flags: --strict alone turns one on, and an explicit value needs = (--strict=false).

Exit codeMeaning
0Success, including printing help.
1The command ran and failed: an invalid config, an unhealthy target, an unreachable database.
2The command line was wrong. Nothing ran.

Building and running

make build                            # bin/hookway and bin/hookway-replay
go run ./cmd/hookway help             # or run straight from the checkout
docker run --rm hookway:local help    # each image's entrypoint is its binary

The core image runs hookway with no command (serve). The replay image runs hookway-replay serve by default. Pass commands supported by that image's binary, for example: docker run --rm hookway-replay:local migrate --config /etc/hookway-replay/replay.yaml.

The browser builder uses a separate WebAssembly artifact, not a terminal program. Its source entrypoint is cmd/hookway-wasm.

Recipes

TaskCommand
Fail CI on any config problem, lint warnings included, without secretshookway validate --config config.yaml --json --no-env --strict
Get completion and inline errors in your editorhookway schema > hookway.schema.json, then # yaml-language-server: $schema=./hookway.schema.json at the top of the config
Draw a flow and export its YAMLhookway editor, then open http://localhost:8090/builder
Check a container from inside ithookway healthcheck --url http://127.0.0.1:8080/readyz
Upgrade the replay servicehookway-replay migrate, then roll out hookway-replay serve
Remove expired replay recordshookway-replay gc
See every missed delivery and retry onethe replay console/API; Local development walks through it with curl

hookway

CommandWhat it does
serveServe webhooks. The default: hookway alone runs it.
import-openapiGenerate MCP tools from selected OpenAPI operations.
validateCheck a config without starting anything.
schemaPrint the config JSON Schema, or the builder's per-type catalogs.
editorServe the config builder and the config API.
healthcheckProbe a running server.
versionPrint the version and commit.
VariableEffect
HOOKWAY_CONFIGThe config file when --config is not given (serve, validate, editor).
HOOKWAY_LOG_LEVELOverrides logging.level at start: debug, info, warn, or error.

Values inside the config come from the environment too, as ${VAR} references; see configuration.md.

hookway serve

hookway [--config FILE]
hookway serve [--config FILE]
FlagMeaning
--config FILEThe config file. Default: $HOOKWAY_CONFIG.
--versionPrint the version and exit without serving.

Loads and validates the config before connecting to any destination, then serves every flow on server.addr. A config that does not validate exits 1 and names each problem, exactly as hookway validate would.

HOOKWAY_CONFIG=examples/config.yaml go run ./cmd/hookway
go run ./cmd/hookway --config examples/config.yaml
make run                                              # the same, with examples/config.yaml

hookway import-openapi

hookway import-openapi --spec FILE --list [--json]
hookway import-openapi --spec FILE --settings FILE [--out FILE]

Inspect local OpenAPI 3.0/3.1 JSON/YAML or generate a new standalone configuration. --settings selects operations and supplies endpoint authentication, upstream credentials and access grants. --out creates a new file without overwriting; omitted output goes to stdout. --json is available only with --list. No network requests, environment expansion or secret reads occur. Unsupported selections fail without partial YAML. See OpenAPI import for supported constructs, settings, examples and result envelopes.

hookway validate

hookway validate [--config FILE] [--json] [--no-env] [--strict]
FlagMeaning
--config FILEThe config file to check. Default: $HOOKWAY_CONFIG.
--jsonPrint {"issues": [...]} on stdout instead of text. An unset ${VAR} becomes a warning.
--no-envLeave ${VAR} unexpanded: check syntax and structure only.
--strictFail on lint warnings too.

Checks the config the way serve would, without connecting to anything: YAML syntax, unknown fields, required settings, durations, duplicate or overlapping flow paths, unknown destinations, node wiring, auth settings, and the rest of the rules in configuration.md. make validate runs it on examples/config.yaml; make validate-examples runs it on every shipped example.

A valid config prints config valid: FILE and exits 0. An invalid one prints every issue on stderr and exits 1.

Environment. Plain validate expands ${VAR} references against the process environment, like serve, and fails with exit 1 if any referenced variable is unset. --json expands too, but reports a missing variable as a config.env.<NAME> issue at severity warning and carries on as if it had a value: CI can validate without production secrets and still fail on a real structural error. --no-env skips expansion in either mode, for a pure syntax and structure check (a config template, say).

Warnings. A config whose only issues are warnings exits 0; the warnings are printed, or listed in the JSON envelope. --strict makes them fail with exit 1. It is the only way to gate a build on the policy lints:

Warnings include a correction in both text and JSON output. These supported choices remain usable, including intentional no_auth, asynchronous delivery, and disposable sample setups; --strict is an explicit operator policy gate. Local HTTP, private destinations, and TLS termination at a trusted proxy are supported without a special production mode. Impossible timeout relationships, missing required credentials, and other structural errors always fail.

JSON output. {"issues": [ ... ]}, where each issue has field (a dotted config path), message, an optional suggestion, and severity ("error" or "warning"). An empty array means valid. The exit code is 1 when any issue is an error, or any issue at all under --strict; 0 otherwise.

--json, --no-env, and --strict combine freely:

hookway validate --config examples/config.yaml
hookway validate --config config.yaml --strict                  # lint warnings fail too
hookway validate --config config.yaml --json                    # CI: secrets optional
hookway validate --config config.template.yaml --json --no-env --strict

hookway schema

hookway schema [--nodes | --destinations | --connections]
FlagMeaning
--nodesPrint each node type's ports and option JSON Schema.
--destinationsPrint each destination type's option JSON Schema.
--connectionsPrint each connection type's option JSON Schema.

With no flag, prints the JSON Schema for the whole config file. It is generated from the config structs, so it cannot drift from the code. Point your editor at it for completion and inline validation:

# yaml-language-server: $schema=./hookway.schema.json

The three flags print the per-type catalogs the config builder uses instead. Pass one; if several are set, the first in the order above wins. make node-schema writes them to web/src/graph/{node,destination,connection}Schema.json, and make node-schema-check fails if the committed files drifted from the binary.

hookway schema > hookway.schema.json
hookway schema --nodes

hookway editor

hookway editor [--addr ADDR] [--config FILE]
FlagMeaning
--addr ADDRThe address to listen on. Default: :8090.
--config FILEA config whose security.api_key protects the API. Default: $HOOKWAY_CONFIG; with neither, the API is open.

Serves the config builder and the API behind it:

POST /validate never expands ${VAR}. The body is untrusted client input, and the API allows any origin (CORS *). If it expanded environment references, a client could post a config full of ${AWS_SECRET_ACCESS_KEY}-style probes and infer server-side secrets and internal URLs from which fields come back populated. So a ${VAR} in a posted config is an opaque literal, and you never see a config.env.<NAME> warning here. For env-expanding validation, run validate --json where the environment lives.

With a config that sets security.api_key, GET /schema, GET /node-schema, and POST /validate require Authorization: Bearer <key>. OPTIONS preflight is always allowed, so browser CORS works. A config given but invalid exits 1.

The builder UI is embedded only in a locally built binary after make ui (run make web-install first on a clean clone). Published core and replay images are runtime-only and do not include the builder bundle. Without the bundle, / and /builder explain that the UI was not built; the API works either way.

hookway editor                                    # http://localhost:8090/builder
hookway editor --addr :9000 --config config.yaml

hookway healthcheck

hookway healthcheck [--url URL] [--timeout DURATION]
FlagMeaning
--url URLThe URL to GET. Default: http://127.0.0.1:8080/healthz.
--timeout DURATIONGive up after this long. Default: 5s.

Exits 0 for any 2xx and 1 for a connection failure, a timeout, or anything else. The container image runs it as its HEALTHCHECK, which is why a scratch image with no shell or curl can still be probed. /healthz is liveness only; point --url at /readyz to include the destinations.

hookway version

hookway version

Prints hookway <version> (<commit>). A plain go build reports dev (none); make build, make docker-build, and the release workflow inject the real values from git describe and git rev-parse. hookway --version prints the same line.

hookway-replay

The replay service is optional; core runs the same without it. Its API, configuration, outcomes, and runbook are in replay-service.md. Every command takes its config from --config, or from HOOKWAY_REPLAY_CONFIG, and ${VAR} references in the file are expanded from the environment.

CommandWhat it does
serveRun the API, the retry workers, and the retention sweep.
migrateApply database migrations.
gcDelete expired records once.
validateCheck a config without connecting to anything.
healthcheckProbe a running service.
versionPrint the version and commit.
VariableEffect
HOOKWAY_REPLAY_CONFIGThe config file when --config is not given.
HOOKWAY_LOG_LEVELOverrides logging.level at start: debug, info, warn, or error.

Run validate before deployment and migrate once before starting the matching serve replicas. Use a one-shot container or ECS task for migration; the local Compose examples include a migrate service.

hookway-replay serve

hookway-replay serve [--config FILE]
FlagMeaning
--config FILEThe config file. Default: $HOOKWAY_REPLAY_CONFIG.

Runs, until SIGINT or SIGTERM:

It exits 1 when the database is unreachable or its schema is incompatible; run the matching migration before serving. Shutdown allows 30s for the API and the remaining lease for claimed retries. Set container grace above max(30s, workers.lease) with margin; see Upgrades. Logs are JSON on stdout.

hookway-replay serve --config /etc/hookway-replay/replay.yaml
HOOKWAY_REPLAY_CONFIG=replay.yaml hookway-replay serve

hookway-replay migrate

hookway-replay migrate [--config FILE]
FlagMeaning
--config FILEThe config file. It must validate, though only database is used. Default: $HOOKWAY_REPLAY_CONFIG.

Applies every pending migration and prints applied migrations [...]; schema is at version N, or schema is current at version N when there was nothing to do. Replicas may run it at the same time: a PostgreSQL advisory lock serializes them. Exits 1 when the database is unreachable or a migration fails.

hookway-replay migrate --config replay.yaml

The initial generic-record schema includes operator resolutions and inspections. It has database migration version 1 and uses API/WASM contract version 1; these version numbers describe different contracts. Initialize a fresh database before starting matching core, replay and console binaries. Existing failure-shaped databases are outside this baseline's compatibility scope. The service never applies migrations automatically. Operator resolution is an authenticated HTTP operation, not a command that bypasses API scopes; see Replay investigation.

hookway-replay gc

hookway-replay gc [--config FILE]
FlagMeaning
--config FILEThe config file. Default: $HOOKWAY_REPLAY_CONFIG.

Deletes the records past retention.ttl once, prints gc deleted N records and freed M bytes, and exits. serve already sweeps on a timer, so this is for scheduled cleanup or an extra sweep of already expired records. Shortening TTL affects new captures, not existing expiry timestamps. When another replica holds the sweep lock, it prints gc skipped: another replica is sweeping and exits 0.

hookway-replay gc --config replay.yaml

hookway-replay validate

hookway-replay validate [--config FILE]
FlagMeaning
--config FILEThe config file to check. Default: $HOOKWAY_REPLAY_CONFIG.

Checks the config without connecting to the database or to core: unknown fields, durations, limits, key names and scopes, token lengths (32 characters at least), and URLs. ${VAR} references are expanded, and an unset variable fails. Enabled metrics without a metrics-scoped key is an error. Warnings are printed on stderr but pass. Prints config valid: FILE and exits 0, or lists the issues and exits 1.

hookway-replay validate --config examples/dev/replay.yaml

hookway-replay healthcheck

hookway-replay healthcheck [--url URL] [--timeout DURATION]
FlagMeaning
--url URLThe URL to GET. Default: http://127.0.0.1:8080/healthz.
--timeout DURATIONGive up after this long. Default: 5s.

Exits 0 for any 2xx and 1 otherwise. The container image runs it as its HEALTHCHECK. /healthz is liveness only; /readyz also checks that the database answers and its schema is current.

hookway-replay version

hookway-replay version

Prints hookway-replay <version> (<commit>). A plain go build reports dev (none).

hookway-wasm

cmd/hookway-wasm is the same config validator and schema, compiled to WebAssembly so the config builder validates client-side instead of round-tripping to a server.

Build it:

make wasm

That runs GOOS=js GOARCH=wasm go build ./cmd/hookway-wasm, writes hookway.wasm and the matching wasm_exec.js into internal/editor/assets/, and copies both into web/public/ where Vite serves them. Both artifacts are generated and gitignored. make ui, make web-dev, and make web-e2e all build it first: without it the dev server 404s hookway.wasm. The deployed product builder uses local WASM only and offers retry when validation is unavailable. The optional Go-hosted local editor retains its POST /validate fallback; a frontend-only development server has no editor API.

Browser globals

main installs three functions on globalThis and then blocks forever so they stay callable:

GlobalArgumentsReturns
hookwayValidate(yaml)one string: candidate config YAMLJSON string {"issues":[...]}: the same envelope as validate --json --no-env
hookwaySchema()noneJSON string: the config JSON Schema (hookway schema)
hookwayNodeSchemas()noneJSON string: the per-node catalog (hookway schema --nodes)

An internal failure (a marshalling error, or a bad argument type) returns a JSON string with a top-level "error" key instead, which is how the JS side tells it apart from a validation response's "issues".

All three delegate to cmd/hookway-wasm/bridge, a pass-through of internal/configapi that adds no validation logic of its own. That is the point: an in-browser result is byte-identical to the CLI's and to the HTTP editor's, because all three share one configapi entry point. bridge has no syscall/js import, so it is unit-testable on the host.

Like the HTTP editor, hookwayValidate never expands ${VAR}: browser-supplied bytes are untrusted. See hookway editor for why.

Standalone harness

A single static page (cmd/hookway-wasm/harness/index.html, no npm dependencies) that loads the module and gives you a YAML textarea plus hookwayValidate() and hookwaySchema() buttons, for exercising the validator without running Vite or the editor:

make wasm-harness

This rebuilds the WASM (it depends on wasm), copies hookway.wasm and wasm_exec.js next to cmd/hookway-wasm/harness/index.html, and serves that directory on http://localhost:8091. Ctrl-C stops it.

The harness uses python3 -m http.server, so python3 must be on your PATH. A plain file:// open will not work: browsers refuse to instantiate WebAssembly from a file: origin, which is why the target serves over HTTP at all.

hookway state-store

Operate a shared protection store without starting listeners or opening destinations.

hookway state-store migrate --config core.yaml --store cluster
hookway state-store status --config core.yaml --store cluster
hookway state-store gc --config core.yaml --store cluster
hookway state-store policy-update --config desired-core.yaml --store cluster --policy customer-quota --expected-fingerprint CURRENT_HASH

--config FILE selects the configuration (defaults to HOOKWAY_CONFIG); --store NAME selects a named state_stores backend. --timeout DURATION bounds the operator command, including migration or maintenance work. --policy ID selects a policy for coordinated update; --expected-fingerprint HASH requires the current stored definition to match before changing it. Status prints stored fingerprints and maintenance state. Migration is PostgreSQL-only and is never run by serve. GC deletes expired entries only. A CLI process cannot administer another process's memory store. See Protection stores for permissions, update/resume behavior and deployment cautions.

The replay binary serves only the scoped API and workers. Build it with make build without web dependencies. Deploy hookway-console separately for browser operations; its REPLAY_API_URL and replay server.cors.allowed_origins connect the services. See replay-console.md.

event-store

hookway event-store migrate|status|gc --config FILE administers the optional durable MCP event store. Migrate explicitly creates its schema and tables under an advisory migration lock; serving never migrates. Status prints only the schema version. GC removes terminal delivery records older than 24 hours and unreferenced event bodies. See MCP events.