11 KiB
name, description, version, metadata
| name | description | version | metadata | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| webhook-subscriptions | Webhook subscriptions: event-driven agent runs. | 1.3.0 |
|
Webhook Subscriptions
Create dynamic webhook subscriptions so external services (GitHub, GitLab, Stripe, CI/CD, IoT sensors, monitoring tools) can trigger Hermes agent runs by POSTing events to a URL.
Setup (Required First)
The webhook platform must be enabled before subscriptions can be created. Check with:
hermes webhook list
If it says "Webhook platform is not enabled", set it up:
Option 1: Setup wizard
hermes gateway setup
Follow the prompts to enable webhooks, set the port, and set a global HMAC secret.
Option 2: Manual config
Add to ~/.hermes/config.yaml:
platforms:
webhook:
enabled: true
extra:
host: "0.0.0.0"
port: 8644
secret: "generate-a-strong-secret-here"
Option 3: Environment variables
Add to ~/.hermes/.env:
WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644
WEBHOOK_SECRET=generate-a-strong-secret-here
After configuration, start (or restart) the gateway:
hermes gateway run
# Or if using systemd:
systemctl --user restart hermes-gateway
Verify it's running:
curl http://localhost:8644/health
Commands
All management is via the hermes webhook CLI command:
Create a subscription
hermes webhook subscribe <name> \
--prompt "Prompt template with {payload.fields}" \
--events "event1,event2" \
--description "What this does" \
--skills "skill1,skill2" \
--deliver telegram \
--deliver-chat-id "12345" \
--secret "optional-custom-secret"
Returns the webhook URL and HMAC secret. The user configures their service to POST to that URL.
List subscriptions
hermes webhook list
Remove a subscription
hermes webhook remove <name>
Test a subscription
hermes webhook test <name>
hermes webhook test <name> --payload '{"key": "value"}'
Prompt Templates
Prompts support {dot.notation} for accessing nested payload fields:
{issue.title}— GitHub issue title{pull_request.user.login}— PR author{data.object.amount}— Stripe payment amount{sensor.temperature}— IoT sensor reading
If no prompt is specified, the full JSON payload is dumped into the agent prompt.
Common Patterns
GitHub: new issues
hermes webhook subscribe github-issues \
--events "issues" \
--prompt "New GitHub issue #{issue.number}: {issue.title}\n\nAction: {action}\nAuthor: {issue.user.login}\nBody:\n{issue.body}\n\nPlease triage this issue." \
--deliver telegram \
--deliver-chat-id "-100123456789"
Then in GitHub repo Settings → Webhooks → Add webhook:
- Payload URL: the returned webhook_url
- Content type: application/json
- Secret: the returned secret
- Events: "Issues"
GitHub: PR reviews
hermes webhook subscribe github-prs \
--events "pull_request" \
--prompt "PR #{pull_request.number} {action}: {pull_request.title}\nBy: {pull_request.user.login}\nBranch: {pull_request.head.ref}\n\n{pull_request.body}" \
--skills "github-code-review" \
--deliver github_comment
Stripe: payment events
hermes webhook subscribe stripe-payments \
--events "payment_intent.succeeded,payment_intent.payment_failed" \
--prompt "Payment {data.object.status}: {data.object.amount} cents from {data.object.receipt_email}" \
--deliver telegram \
--deliver-chat-id "-100123456789"
CI/CD: build notifications
hermes webhook subscribe ci-builds \
--events "pipeline" \
--prompt "Build {object_attributes.status} on {project.name} branch {object_attributes.ref}\nCommit: {commit.message}" \
--deliver discord \
--deliver-chat-id "1234567890"
Generic monitoring alert
hermes webhook subscribe alerts \
--prompt "Alert: {alert.name}\nSeverity: {alert.severity}\nMessage: {alert.message}\n\nPlease investigate and suggest remediation." \
--deliver origin
Alertmanager: monitoring alerts with automatic RCA
Alertmanager sends a grouped payload with status, commonLabels, commonAnnotations, and an alerts array. Use the top-level common fields in the template — individual alerts inside the alerts array are NOT accessible via dot notation.
hermes webhook subscribe alertmanager-rca \
--prompt 'Alertmanager Event - Status: {status}
Severity: {commonLabels.severity}
Alert: {commonLabels.alertname}
Instance: {commonLabels.instance}
Summary: {commonAnnotations.summary}
Description: {commonAnnotations.description}
Perform a root cause analysis. Investigate the affected system using available
tools (Prometheus queries, SSH to hosts, API calls). Report findings concisely.' \
--description "Alertmanager alerts → automatic RCA" \
--deliver telegram \
--deliver-chat-id "<your-chat-id>"
Alertmanager config (receiver side):
route:
receiver: hermes-rca
group_by: ['alertname', 'instance']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receivers:
- name: hermes-rca
webhook_configs:
- url: http://<hermes-ip>:8644/webhooks/alertmanager-rca
send_resolved: true
max_alerts: 0
Pitfall: After changing Prometheus alerting rules, stale alerts can remain in Alertmanager's cache. Restart Alertmanager (docker restart alertmanager) to flush them.
Pitfall — Alertmanager auth gap: Alertmanager 0.27.0 supports http_config.authorization (Bearer/Basic) but does NOT support custom headers like X-Gitlab-Token or X-Hub-Signature-256. Hermes historically validated only GitHub/GitLab/Svix/generic HMAC headers — NOT Authorization: Bearer. This means an Alertmanager → Hermes webhook will get HTTP 401 "Invalid signature" even with the correct secret configured. Fix: either (a) patch gateway/platforms/webhook.py _validate_signature() to accept Authorization: Bearer <secret> as a plain-token match, or (b) set the route secret to INSECURE_NO_AUTH (loopback only). See references/alertmanager-auth-integration.md for the full patch and config.
See proxmox-ve-administration skill → references/alertmanager-webhook-setup.md for the full setup guide including PVE-specific alerting rule pitfalls.
Direct delivery (no agent, zero LLM cost)
For use cases where you just want to push a notification through to a user's chat — no reasoning, no agent loop — add --deliver-only. The rendered --prompt template becomes the literal message body and is dispatched directly to the target adapter.
Use this for:
- External service push notifications (Supabase/Firebase webhooks → Telegram)
- Monitoring alerts that should forward verbatim
- Inter-agent pings where one agent is telling another agent's user something
- Any webhook where an LLM round trip would be wasted effort
hermes webhook subscribe antenna-matches \
--deliver telegram \
--deliver-chat-id "123456789" \
--deliver-only \
--prompt "🎉 New match: {match.user_name} matched with you!" \
--description "Antenna match notifications"
The POST returns 200 OK on successful delivery, 502 on target failure — so upstream services can retry intelligently. HMAC auth, rate limits, and idempotency still apply.
Requires --deliver to be a real target (telegram, discord, slack, github_comment, etc.) — --deliver log is rejected because log-only direct delivery is pointless.
Security
- Each subscription gets an auto-generated HMAC-SHA256 secret (or provide your own with
--secret) - The webhook adapter validates signatures on every incoming POST
- Static routes from config.yaml cannot be overwritten by dynamic subscriptions
- Subscriptions persist to
~/.hermes/webhook_subscriptions.json
How It Works
hermes webhook subscribewrites to~/.hermes/webhook_subscriptions.json- The webhook adapter hot-reloads this file on each incoming request (mtime-gated, negligible overhead)
- When a POST arrives matching a route, the adapter formats the prompt and triggers an agent run
- The agent's response is delivered to the configured target (Telegram, Discord, GitHub comment, etc.)
CRITICAL: Webhook Agent Toolset Is Restricted by Default
The default hermes-webhook toolset (_HERMES_WEBHOOK_SAFE_TOOLS in toolsets.py) only includes web_search, web_extract, vision_analyze, and clarify — no terminal, no file, no SSH, no code execution. This is intentional for security (webhook payloads are untrusted), but it means a webhook-triggered agent cannot investigate anything locally.
If the webhook use case requires local investigation (RCA, SSH to hosts, file reads, Prometheus queries), you MUST add a webhook entry to platform_toolsets in ~/.hermes/config.yaml:
platform_toolsets:
cli:
- browser
- terminal
- file
- web
# ... (full list)
webhook:
- browser
- code_execution
- delegation
- file
- memory
- session_search
- skills
- terminal
- todo
- vision
- web
After adding this, restart the gateway (hermes gateway restart from a separate shell — cannot restart from inside the gateway process). The webhook-triggered agent will then have the same investigative tools as a CLI session.
Without this config change, webhook-triggered agents can only search the web and analyze images — they cannot run commands, read files, or SSH anywhere.
Troubleshooting
If webhooks aren't working:
- Is the gateway running? Check with
systemctl --user status hermes-gatewayorps aux | grep gateway - Is the webhook server listening?
curl http://localhost:8644/healthshould return{"status": "ok"} - Check gateway logs:
grep webhook ~/.hermes/logs/gateway.log | tail -20 - Signature mismatch? Verify the secret in your service matches the one from
hermes webhook list. GitHub sendsX-Hub-Signature-256, GitLab sendsX-Gitlab-Token. Alertmanager sendsAuthorization: Bearer <credentials>viahttp_config.authorization— Hermes supports this only if theAuthorization: Bearerpatch is applied (seereferences/alertmanager-auth-integration.md); otherwise it returns 401 "Invalid signature". - Cannot restart gateway from inside the gateway? If you're running inside the Hermes process (e.g., a CLI session served by the gateway),
systemctl --user restart hermes-gatewayis blocked by a safety guard (SIGTERM would kill the running command). Workaround: create a one-shot cronjob withschedule: '1m'that runs the restart command — it executes in a fresh session outside the gateway process. Clean up the cronjob afterwards. - Firewall/NAT? The webhook URL must be reachable from the service. For local development, use a tunnel (ngrok, cloudflared).
- Wrong event type? Check
--eventsfilter matches what the service sends. Usehermes webhook test <name>to verify the route works. - Agent can't investigate? The default webhook toolset is restricted (see "CRITICAL" section above). Add
webhooktoplatform_toolsetsin config.yaml to grant terminal/file/web access.