Introduction
Hermes Agent is more than a conversational assistant — it''s also an automation engine capable of reacting to external events. Webhooks are the bridge between the outside world and your Hermes instance. Whether it''s a Pull Request on GitHub, a monitoring alert, a form submission, or a CI/CD pipeline completion, webhooks allow Hermes to receive these signals and respond intelligently.
This article provides an in-depth exploration of Hermes'' integrated webhook system — from configuration in config.yaml to security mechanisms, concrete use cases, and troubleshooting best practices. This is an advanced guide that assumes familiarity with Hermes Agent fundamentals, covered in the introduction article and advanced configuration.
What Are Webhooks in Hermes?
A webhook is a mechanism where an external service sends an HTTP POST request to a URL you expose, containing a JSON payload describing an event. In Hermes, the Webhook adapter (gateway/platforms/webhook.py) is a platform adapter just like Telegram or Discord: it runs inside the multi-platform gateway and benefits from the same lifecycle, session management, and response delivery mechanisms.
Specifically, Hermes starts an HTTP server (aiohttp) on a configurable port (default 8644) and listens for POST requests on /webhooks/{route_name}. Each route is declared in config.yaml and defines which events to accept, how to transform the payload into an agent prompt, which skills to load, and where to deliver the response.
Webhook Architecture in Hermes
External service (GitHub, GitLab, Sentry…)
│
▼ HTTP POST
┌─────────────────────────────┐
│ WebhookAdapter (aiohttp) │
│ ┌─ HMAC validation │
│ ├─ Rate limiting │
│ ├─ Idempotency cache │
│ ├─ Event filtering │
│ ├─ Prompt template render │
│ ├─ Skill injection │
│ └─ Agent run (202 Accepted) │
└─────────────────────────────┘
│
▼
Response delivered to:
• Telegram / Discord / Slack (cross-platform)
• GitHub PR comment
• Internal log
The key point: the HTTP response is immediate (202 Accepted), the agent processes the request asynchronously, and the response is routed to the platform of your choice.
Configuration in config.yaml
Webhook configuration lives under the platforms.webhook key in your ~/.hermes/config.yaml. Here''s the full structure:
platforms:
webhook:
enabled: true
extra:
host: "0.0.0.0"
port: 8644
secret: "your-global-hmac-secret"
rate_limit: 30
max_body_bytes: 1048576
routes:
github_pr_review:
secret: "${WEBHOOK_GITHUB_SECRET}"
events:
- pull_request
- pull_request_review
prompt: >
Review this pull request and provide detailed feedback:
PR #{pull_request.number} — {pull_request.title}
Author: {pull_request.user.login}
Body: {pull_request.body}
Diff: {__raw__}
skills:
- code-review
deliver: github_comment
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{pull_request.number}"
monitoring_alert:
secret: "${WEBHOOK_GRAFANA_SECRET}"
events:
- alerting
prompt: >
Analyze this monitoring alert and suggest actions:
{__raw__}
deliver: telegram
deliver_extra:
chat_id: "-1001234567890"
deploy_notification:
secret: "${WEBHOOK_DEPLOY_SECRET}"
events:
- deploy.success
- deploy.failure
prompt: >
Deployment {event_type} on {environment}:
{__raw__}
deliver_only: true
deliver: discord
deliver_extra:
chat_id: "123456789"
Global Parameters
host: listen address (default0.0.0.0)port: HTTP port (default8644)secret: global HMAC secret, used when no route defines its ownrate_limit: max requests per minute per route (default30)max_body_bytes: max payload size in bytes (default1,048,576= 1 MB)
Route Parameters
Each key under routes defines an endpoint at /webhooks/{route_name}:
secret: HMAC secret for this route (required — see security section)events: list of accepted events (filtered byX-GitHub-Event,X-GitLab-Event, orevent_typepayload field)prompt: prompt template with payload interpolationskills: list of skills to inject into the agent''s contextdeliver: delivery target (log,github_comment,telegram,discord,slack, etc.)deliver_extra: delivery parameters (templates interpolated from payload)deliver_only: iftrue, skip the agent — rendered template is delivered directly (push notification, zero LLM cost)
Trigger Events
GitHub Events
GitHub webhooks use the X-GitHub-Event header. Hermes automatically reads this header and compares it to the route''s events list. Common events:
push: a commit is pushedpull_request: a PR is opened, updated, or closedpull_request_review: a review is submittedissues: an issue is created or modifiedrelease: a new release is publishedworkflow_run: a GitHub Action completes
GitLab Events
The equivalent header is X-GitLab-Event. Events include Push Hook, Merge Request Hook, Note Hook, etc.
Custom Events
For any service that doesn''t use GitHub/GitLab headers, Hermes checks the event_type field in the JSON payload. This covers Sentry, Grafana, Supabase, Stripe, or any custom webhook.
CI/CD Pipeline Example
platforms:
webhook:
extra:
routes:
ci_pipeline:
secret: "${WEBHOOK_CI_SECRET}"
events:
- pipeline.success
- pipeline.failure
prompt: >
CI/CD pipeline completed for {project_name}:
Status: {event_type}
Commit: {commit_sha} by {author}
Branch: {branch}
Details: {__raw__}
deliver: telegram
deliver_extra:
chat_id: "-1001234567890"
Handlers: Prompt Templates, Skills, and Context
Prompt Templates
Prompt templates support dot-notation interpolation for navigating nested JSON payloads:
{pull_request.title} → payload["pull_request"]["title"]
{pull_request.user.login} → payload["pull_request"]["user"]["login"]
{repository.full_name} → payload["repository"]["full_name"]
The special {__raw__} token dumps the entire payload as JSON (truncated to 4,000 characters), ideal when the agent needs full visibility into the data.
If no template is provided, Hermes generates a default prompt with the raw JSON payload.
Skill Injection
The skills parameter loads skill content directly into the prompt sent to the agent, bypassing slash commands. This gives the agent specific context for handling the webhook:
routes:
github_pr_review:
skills:
- code-review
prompt: "Review this PR: {pull_request.title}
{pull_request.body}"
The code-review skill is injected with its instructions and prompt template, guiding the agent''s analysis.
deliver_only Mode (Direct Push)
Setting deliver_only: true completely bypasses the agent. The rendered template becomes the message sent directly to the target platform. Use cases:
- Monitoring alerts to Telegram/Discord (< 1 second latency)
- Deployment notifications to a channel
- Inter-agent pings (zero LLM cost)
Security
HMAC Signature Verification
Every route must have a secret configured. Hermes validates the HMAC signature before reading the payload (auth-before-body):
- GitHub: validates
X-Hub-Signature-256(HMAC-SHA256) - GitLab: validates
X-Gitlab-Token(direct comparison) - Generic: validates
X-Webhook-Signature(HMAC-SHA256)
If a secret is configured but no signature header is found, the request is rejected (401 Unauthorized).
Testing Mode (INSECURE_NO_AUTH)
For local development, you can temporarily disable authentication:
routes:
test_route:
secret: "INSECURE_NO_AUTH"
⚠️ Never use in production. This mode skips all signature validation.
Rate Limiting
Hermes enforces per-route rate limiting (fixed 60-second window). Requests beyond the limit receive a 429 Too Many Responses. Default is 30 requests/minute per route.
Idempotency Cache
To prevent duplicates from provider retries, Hermes maintains a cache of delivery IDs (X-GitHub-Delivery or X-Request-ID) with a 1-hour TTL. Duplicate requests receive a 200 with status: "duplicate" instead of triggering a second agent run.
Payload Size Limits
The max_body_bytes parameter (default 1 MB) is checked before reading the body via Content-Length. Oversized payloads receive a 413 Payload Too Large.
Concrete Use Cases
1. Automated Pull Request Review
Configure Hermes to receive GitHub pull_request events and post a review comment directly on the PR:
routes:
pr_review:
secret: "${GITHUB_WEBHOOK_SECRET}"
events: ["pull_request"]
prompt: >
Analyze this pull request carefully.
Check code quality, potential risks, and suggest improvements.
Repo: {repository.full_name}
PR #{pull_request.number}: {pull_request.title}
By: {pull_request.user.login}
Description: {pull_request.body}
skills: ["code-review"]
deliver: github_comment
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{pull_request.number}"
2. Monitoring Alerts to Telegram
Receive Grafana or Sentry alerts and have the AI analyze incidents:
routes:
grafana_alert:
secret: "${GRAFANA_WEBHOOK_SECRET}"
events: ["alerting"]
prompt: >
Monitoring alert received.
Analyze the likely cause and propose an action plan.
{__raw__}
deliver: telegram
deliver_extra:
chat_id: "${ALERTS_CHAT_ID}"
3. Form Submissions
A website sends form submissions to Hermes, which processes and routes them:
routes:
contact_form:
secret: "${FORM_WEBHOOK_SECRET}"
prompt: >
New contact form submission:
Name: {name}
Email: {email}
Subject: {subject}
Message: {message}
Categorize this request and suggest an appropriate response.
deliver: slack
deliver_extra:
chat_id: "C12345SUPPORT"
4. Deployment Notifications (Direct Push)
For deployments, no AI needed — a raw notification suffices:
routes:
deploy:
secret: "${DEPLOY_WEBHOOK_SECRET}"
events: ["deploy.success", "deploy.failure"]
prompt: "🚀 Deployment {event_type} on {environment} (v{version})"
deliver_only: true
deliver: discord
deliver_extra:
chat_id: "${DEPLOYS_CHANNEL}"
GitHub Webhook Integration
GitHub Configuration
- Go to Settings → Webhooks → Add webhook
- Payload URL:
http://your-server:8644/webhooks/github_pr_review - Content type:
application/json - Secret: the same secret as in your
config.yaml - Events: select
Pull requests,Push events, etc.
Secret Security
Store the secret in ~/.hermes/.env and reference it with ${VAR} in the config:
# ~/.hermes/.env
GITHUB_WEBHOOK_SECRET=whsec_abc123def456...
# config.yaml
routes:
github_pr_review:
secret: "${GITHUB_WEBHOOK_SECRET}"
Hermes automatically resolves environment variables in the configuration at startup.
ngrok Tunneling (Development)
For testing locally behind NAT:
ngrok http 8644
# → https://abc123.ngrok.io/webhooks/your_route
Use the ngrok URL as the Payload URL in GitHub settings.
Logs and Monitoring
Adapter Logs
Every webhook request is logged by the adapter:
[webhook] POST event=pull_request route=github_pr_review prompt_len=847 delivery=12345-abcde
[webhook] Posted comment on owner/repo#42
[webhook] Skipping duplicate delivery 12345-abcde
[webhook] Invalid signature for route grafana_alert
Health Check
The GET /health endpoint returns {"status": "ok", "platform": "webhook"}. Use it for your monitoring:
curl http://localhost:8644/health
Dynamic Routes
Hermes supports routes created dynamically by the agent itself. These routes are stored in ~/.hermes/webhook_subscriptions.json and are automatically reloaded on each request (mtime-gated check). Static routes (config.yaml) always take priority over dynamic routes.
Lifecycle Hooks
Hermes'' hook system (gateway/hooks.py) allows reacting to internal gateway events, independent of external webhooks:
gateway:startup— on startupsession:start/session:end— session lifecycleagent:start/agent:step/agent:end— agent loopcommand:*— wildcard for any slash command
You can combine hooks and webhooks: an agent:end hook could log statistics for every webhook response.
Troubleshooting
Port Already in Use
[webhook] Port 8644 already in use.
Change the port in config.yaml:
platforms:
webhook:
extra:
port: 8645
Invalid Signature
[webhook] Invalid signature for route my_route
- Verify the secret in
config.yamlmatches the provider''s configuration exactly - For GitHub, check the
X-Hub-Signature-256header (requires UTF-8 encoded secret) - For GitLab, check the
X-Gitlab-Tokenheader - For generic webhooks, use the
X-Webhook-Signatureheader with the body''s HMAC-SHA256
Route Not Found (404)
Verify that the route name in the URL exactly matches the key in config.yaml. URLs are case-sensitive: /webhooks/GitHub_PR ≠ /webhooks/github_pr.
Delivery Failure
github_comment: install theghCLI and authenticate (gh auth login)- Cross-platform: verify the target platform is enabled in
config.yamland connected - Missing
chat_id: provide achat_idindeliver_extraor configure ahome_channelfor the target platform
Payload Too Large (413)
Increase max_body_bytes if your webhooks send large payloads (e.g., very large PR diffs):
platforms:
webhook:
extra:
max_body_bytes: 5242880 # 5 MB
Rate Limit (429)
If a service sends too many events, adjust the per-route rate_limit:
routes:
busy_service:
rate_limit: 100
Missing aiohttp Dependency
The webhook adapter requires aiohttp. If you get an import error:
pip install aiohttp
✅ Conclusion
Webhooks transform Hermes Agent into a true automation node capable of reacting to events from dozens of external services. The platform adapter architecture ensures that webhooks benefit from the same robust lifecycle as Telegram, Discord, or Slack — with additional advanced security mechanisms (HMAC, rate limiting, idempotency) and the flexibility of cross-platform routing.
To explore Hermes further:
- Cron Jobs and Automation — for scheduled recurring tasks
- Multi-platform Gateway — to understand the full architecture
- Profiles and Configurations — for managing multiple Hermes instances
- Subagent Delegation — for parallelizing complex tasks
- MCP Servers — for extending Hermes via external tools
Conclusion
Hermes Agent webhooks provide a bidirectional gateway between your AI agent and the external ecosystem. While cron jobs allow Hermes to take the initiative at regular intervals, webhooks let third-party services trigger actions in real time. Combined, cron jobs and webhooks cover the full automation spectrum: scheduled tasks on one side, reactive events on the other. Cross-platform routing flexibility, security mechanisms (HMAC, rate limiting), and asynchronous delivery support make webhooks an essential tool for integrating Hermes into any workflow.