Webhook Subscriptions — Webhook subscriptions: event-driven agent runs
Webhook Subscriptions
Section titled “Webhook Subscriptions”Webhook subscriptions: event-driven agent runs.
Skill metadata
Section titled “Skill metadata”| Source | Bundled (installed by default) |
| Path | skills/devops/webhook-subscriptions |
| Version | 1.1.0 |
| Tags | webhook, events, automation, integrations, notifications, push |
Reference: full SKILL.md
Section titled “Reference: full SKILL.md”The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
Webhook Subscriptions
Section titled “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)
Section titled “Setup (Required First)”The webhook platform must be enabled before subscriptions can be created. Check with:
hermes webhook listIf it says “Webhook platform is not enabled”, set it up:
Option 1: Setup wizard
Section titled “Option 1: Setup wizard”hermes gateway setupFollow the prompts to enable webhooks, set the port, and set a global HMAC secret.
Option 2: Manual config
Section titled “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
Section titled “Option 3: Environment variables”Add to ~/.hermes/.env:
WEBHOOK_ENABLED=trueWEBHOOK_PORT=8644WEBHOOK_SECRET=generate-a-strong-secret-hereAfter configuration, start (or restart) the gateway:
hermes gateway run# Or if using systemd:systemctl --user restart hermes-gatewayVerify it’s running:
curl http://localhost:8644/healthCommands
Section titled “Commands”All management is via the hermes webhook CLI command:
Create a subscription
Section titled “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
Section titled “List subscriptions”hermes webhook listRemove a subscription
Section titled “Remove a subscription”hermes webhook remove <name>Test a subscription
Section titled “Test a subscription”hermes webhook test <name>hermes webhook test <name> --payload '{"key": "value"}'Prompt Templates
Section titled “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
Section titled “Common Patterns”GitHub: new issues
Section titled “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
Section titled “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_commentStripe: payment events
Section titled “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
Section titled “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
Section titled “Generic monitoring alert”hermes webhook subscribe alerts \ --prompt "Alert: {alert.name}\nSeverity: {alert.severity}\nMessage: {alert.message}\n\nPlease investigate and suggest remediation." \ --deliver originDirect delivery (no agent, zero LLM cost)
Section titled “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
Section titled “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
Section titled “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.)
Troubleshooting
Section titled “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. - 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.