Appearance
Webhooks: Event-Driven Agent Runs
Cron jobs fire on a schedule. Multi-agent workflows fire when you start them. But a lot of the things you want Hermes to react to do not happen on your clock. A pull request opens. A monitoring alert triggers. A form submission lands in a database. Webhooks close the gap: an external system sends an HTTP POST to Hermes, and Hermes spins up an agent run with the payload wired into the prompt.

What you'll learn
hermes webhook subscriberegisters a webhook endpoint that triggers an agent run on every inbound POST- Payload templating lets you inject fields from the incoming JSON directly into the agent's prompt, branch names, commit SHAs, alert severities
- Webhooks are managed through a full lifecycle: subscribe, list, test, and remove, with test mode for dry runs before connecting to a live sender
- The webhook system pairs with cron and delegation: a webhook fires the trigger, cron provides the schedule, and delegation handles parallelism inside the run
The problem
You want Hermes to run a code review every time a PR opens. You could poll the GitHub API every 60 seconds with a cron job. That works, but it is wasteful , 1,440 polls a day, and 1,435 of them find nothing. It is also slow: a PR can sit for up to 59 seconds before the poll catches it. And polling means Hermes has to re-derive what changed every time, because it never sees the actual event payload with the branch name and commit SHA.
Webhooks flip the model. GitHub calls Hermes when the event happens. Hermes wakes up, reads the payload, and runs the agent with the exact context it needs. No polling, no delay, no wasted model calls.
Options & when to use each
| Approach | Good for | Costs you | When to pick it |
|---|---|---|---|
hermes webhook subscribe | Event-driven triggers from external systems | Requires a publicly reachable Hermes instance (or a tunnel like ngrok for local dev) | PR reviews, alert responses, form submissions, anything where an external system is the source of truth for "when" |
| Cron-based polling | Systems that do not support webhooks, or when you control the timing tightly | Wasted compute, latency up to the poll interval | Legacy APIs with no webhook support, or when you want rate-limited batching of events |
Manual trigger with hermes chat -q fed by your own script | Full control over payload parsing and pre-processing | You write and maintain the integration layer | When the external system's webhook format is messy and you want to normalize it before Hermes sees it |
Build it
Step 1: Subscribe to a webhook
The minimum viable webhook: a named subscription with a prompt. Every POST to the endpoint triggers an agent run with that prompt.
bash
hermes webhook subscribe pr-review \
--prompt "Review the pull request described in the incoming webhook payload. Check for: security issues, breaking API changes, missing tests, and unclear variable names. Post a summary of findings."The command returns a webhook URL, something like https://hermes.your-server.com/webhooks/pr-review. Copy this URL. You will configure GitHub (or whatever system sends the events) to POST to it.
Step 2: Configure the sender
On GitHub, go to your repository Settings → Webhooks → Add webhook. Paste the Hermes webhook URL. Set content type to application/json. Select the events you want, pull request events, push events, or specific actions.
On Linear (issue tracker), go to Workspace Settings → API → Webhooks. Paste the URL. Select issue creation and status change events.
The sender will fire a test ping. Check that Hermes received it:
bash
hermes webhook listActive subscriptions show their endpoint, prompt, and last trigger time. If the test ping was received, the last trigger time updates.
Step 3: Payload templating, inject event fields into the prompt
A raw webhook payload from GitHub is a large JSON blob. The agent does not need all of it. Templating extracts the fields the agent actually cares about and drops them into a clean prompt.
bash
hermes webhook subscribe pr-review \
--prompt "A pull request was just {{.action}}.
Repository: {{.repository.full_name}}
PR #{{.pull_request.number}}: {{.pull_request.title}}
Author: {{.pull_request.user.login}}
Branch: {{.pull_request.head.ref}}
Changed files: {{range .pull_request.requested_reviewers}}{{.login}} {{end}}
Review this PR. Check the diff for:
1. Security issues
2. Breaking API changes
3. Missing tests
4. Unclear variable names
Post findings as a comment on the PR."The {{.field.path}} syntax pulls values from the incoming JSON body. {{.action}} becomes "opened" or "closed". {{.pull_request.title}} becomes the actual PR title. The agent never sees the raw 4KB GitHub payload, it sees a structured prompt with exactly the fields it needs.
Common templating patterns:
| Payload field | Template syntax | What the agent sees |
|---|---|---|
{"action": "opened"} | {{.action}} | opened |
{"pull_request": {"title": "Fix auth bug"}} | {{.pull_request.title}} | Fix auth bug |
{"commits": [{"id": "abc123", "message": "..."}]} | {{range .commits}}{{.id}}: {{.message}}\n{{end}} | abc123: Fix login race condition |
{"alert": {"severity": "critical", "service": "api"}} | Alert: {{.alert.severity}} on {{.alert.service}} | Alert: critical on api |
Step 4: Test a webhook before connecting a live sender
Use the test command to simulate an incoming payload without configuring an external service:
bash
hermes webhook test pr-review \
--payload '{"action":"opened","pull_request":{"number":42,"title":"Add rate limiting","head":{"ref":"feature/rate-limit"},"user":{"login":"alice"}},"repository":{"full_name":"myorg/api"}}'This fires a one-shot agent run with the test payload. Check the output, does the agent produce a review based on the mock PR data? If the template syntax is wrong, you will see placeholder text in the agent's prompt instead of actual values. Fix the template and test again.
Step 5: Remove or disable a webhook
Disable a webhook temporarily for testing or remove it permanently when it is no longer needed.
bash
# Stop receiving events but keep the subscription
hermes webhook test pr-review --dry-run
# Remove the subscription entirely
hermes webhook remove pr-reviewAfter removal, the endpoint stops accepting requests. External systems calling it will get a 404.
What goes wrong
| Mistake | How you notice it | The fix |
|---|---|---|
| Template field path does not match the actual payload structure | The agent's prompt contains literal {{.pull_request.title}} instead of the actual title | Test with a real payload: hermes webhook test <name> --payload '...'. Check the output, if template placeholders appear unrendered, the field path is wrong. Compare against the actual JSON structure from the sender's webhook documentation |
| The webhook endpoint is not publicly reachable | The sender reports connection refused or timeout, and hermes webhook list shows no recent triggers | For local development, use a tunnel: ngrok http 8080 and point the sender at the ngrok URL. For production, make sure Hermes is running on a server with a public IP or behind a reverse proxy |
| The sender sends a payload format Hermes was not expecting | The agent run starts but the prompt is missing key fields, and the agent produces a generic or wrong response | Create separate webhook subscriptions for different event types. A pr-opened subscription for PR creation events and a pr-merged subscription for merge events, each with its own template tailored to that event's payload shape |
| A webhook fires too frequently and overwhelms the agent | hermes webhook list shows dozens of triggers in a short window, and agent runs start queuing or timing out | Add rate limiting at the Hermes config level: hermes config set webhooks.rate_limit "5 per minute". Or configure the sender (GitHub, Linear) to batch events or throttle delivery |
| A webhook subscription gets orphaned, you remove the integration on the sender side but forget to remove the Hermes subscription | hermes webhook list shows active subscriptions that never fire | Periodically audit: hermes webhook list. Remove subscriptions that have not triggered in the past 30 days, or that correspond to projects you no longer work on |
Confirm it worked
Verify the webhook pipeline by subscribing to a test endpoint and sending a mock payload. Check that the agent processes the injected data correctly.
bash
# 1. Subscribe a test webhook
hermes webhook subscribe hello-webhook \
--prompt "You received a webhook. The message is: {{.message}}. Respond with a confirmation that includes the message text."
# 2. Test it with a mock payload
hermes webhook test hello-webhook \
--payload '{"message":"Hermes webhooks are working"}'
# 3. Verify the agent's response mentions "Hermes webhooks are working"
# If it does, templating and agent execution are both working.
# 4. Clean up
hermes webhook remove hello-webhookIf the test run produces a response that includes the message text from your payload, the webhook pipeline, subscribe, receive, template, execute, is working end to end.
Next: Profiles, Isolated Agent Contexts , keeping separate projects walled off with their own models, skills, and cron tables.