Wiki · Features in depth

Webhooks

A webhook is a door into your agent. How to lock it, whom to open it for and what the agent should do when something knocks.

✅ Verified 17. 10. 2026Source: official documentation →📋 Copy for your AI
🪝
A webhook is a door into your agent. Locked it is useful; open to the internet it is a liability. Hermes verifies a signature on every request (HMAC), but never expose port 8644 directly to the world. Keep webhooks behind a firewall or a reverse proxy with HTTPS — and keep the secret in .env, not in git.

What it is for

The agent no longer has to wait to be asked. When something happens in your app (a new pull request, a paid order, a new article, an error in the logs), the app sends a request, Hermes starts working on it — and delivers the result wherever you point it.

Typical useWhat the agent does
GitHub: a new pull requestReads the diff, writes a review and posts it as a PR comment
Push to the main branchChecks the site, runs tests, messages Telegram that it is done
From your app: a completed paymentIssues a receipt, sends an e-mail, creates a task
Monitoring: a service went downReads the logs and sends a summary of what happened

Enabling

# ~/.hermes/.env WEBHOOK_ENABLED=true WEBHOOK_PORT=8644 # default WEBHOOK_SECRET=your-global-secret
# or via the wizard (enables webhooks, port and global secret) hermes gateway setup # verify the server is up curl http://localhost:8644/health # → {"status": "ok", "platform": "webhook"}

Routes

Every event source gets its own named route. Your system then points at http://your-server:8644/webhooks/<route-name>.

PropertyMeaning
eventsWhich event types to accept (e.g. ["pull_request"]); empty = all
secretRequired. Signature key; the global one is the fallback
promptInstructions for the agent with payload values ({pull_request.title})
filtersPayload filter — a non-match never wakes the agent
skillsWhich skills the agent should load for this route
deliverWhere the answer goes: telegram, discord, github_comment, email, log (default)…
deliver_onlySkips the agent — the message is just forwarded (zero model cost)
cron_jobRuns an existing scheduled job instead of a fresh session
coalesceDebounces rapid events about the same entity into one run (e.g. 5 commits in a PR)

Example: pull request review

platforms: webhook: enabled: true extra: port: 8644 secret: "global-fallback-secret" routes: github-pr: events: ["pull_request"] secret: "github-webhook-secret" prompt: | Review this pull request: Repository: {repository.full_name} PR #{number}: {pull_request.title} Author: {pull_request.user.login} URL: {pull_request.html_url} Diff: {pull_request.diff_url} Action: {action} skills: ["github-code-review"] deliver: "github_comment" deliver_extra: repo: "{repository.full_name}" pr_number: "{number}" deploy-notify: events: ["push"] secret: "deploy-secret" prompt: "New push to {repository.full_name} branch {ref}: {head_commit.message}" filters: - field: "ref" equals: "refs/heads/main" deliver: "telegram"

Routes can also be created from the CLI

hermes webhook subscribe <name> # creates a route at runtime hermes webhook subscribe <name> --route-profile coder # for a specific profile

A route created from the CLI cannot grant itself more power — toolsets are only changed by hand in config.yaml. That is deliberate: otherwise an agent could use a webhook to give itself more than it has.

Security: signed does not mean trusted

RuleWhy
Never expose port 8644 directlyPut it behind a reverse proxy with HTTPS, or keep it on localhost/VPN
INSECURE_NO_AUTH only for testingIt disables signature checks — never in production
A separate secret per routeIf the GitHub secret leaks, the other routes stay closed
Payload content is untrusted textA PR description can contain instructions (“ignore your rules and…”). The agent must not follow them
Keep events and filters tightFewer wake-ups means lower cost and a smaller attack surface

Things to watch

SituationWhat to do
Webhooks do nothing but /health answersLikely a signature mismatch (different secret) or the event failed events/filters — you get 200 with "ignored"
Port 8644 is takenSomething else uses it; change WEBHOOK_PORT and update the source URL
The agent stops reacting after a restartThe gateway serves webhooks — it must be running (hermes gateway status)
Too many runs from one PRAdd coalesce (key e.g. pull_request.number)
You only want to forward a messagedeliver_only: true — no agent, no model cost

Where next

← Back to the wiki