Skip to content

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.

Gnome mascot

What you'll learn

  • hermes webhook subscribe registers 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

ApproachGood forCosts youWhen to pick it
hermes webhook subscribeEvent-driven triggers from external systemsRequires 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 pollingSystems that do not support webhooks, or when you control the timing tightlyWasted compute, latency up to the poll intervalLegacy APIs with no webhook support, or when you want rate-limited batching of events
Manual trigger with hermes chat -q fed by your own scriptFull control over payload parsing and pre-processingYou write and maintain the integration layerWhen 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 list

Active 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 fieldTemplate syntaxWhat 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-review

After removal, the endpoint stops accepting requests. External systems calling it will get a 404.

What goes wrong

MistakeHow you notice itThe fix
Template field path does not match the actual payload structureThe agent's prompt contains literal {{.pull_request.title}} instead of the actual titleTest 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 reachableThe sender reports connection refused or timeout, and hermes webhook list shows no recent triggersFor 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 expectingThe agent run starts but the prompt is missing key fields, and the agent produces a generic or wrong responseCreate 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 agenthermes webhook list shows dozens of triggers in a short window, and agent runs start queuing or timing outAdd 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 subscriptionhermes webhook list shows active subscriptions that never firePeriodically 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-webhook

If 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.