一个 cron 任务只回答一个问题:「早上 6 点运行。」而一个 webhook 回答的是更好的那个问题:「事情一发生就立刻运行。」对于一个要分流 GitHub issues、响应 Stripe 支付、对监控告警做出反应、或回复 im-bot 消息的 AI Agent 来说,轮询是在浪费延迟和 token。本教程用 webhook 订阅把 Hermes Agent 接到外部世界——外部服务 POST 一个事件,Hermes 就地启动一次 Agent 运行,由 sun-port 加固、记录在 PostgreSQL 里、用 Docker 发布、并用 Git 做版本控制。
Cron 和 webhook 是互补的,而非竞争关系。Cron 处理定时的工作——每天运行的内容管线、每晚的备份、每周的报告。Webhook 处理响应式的工作——一个事件到来,Agent 必须在几秒内响应。
下面是我们要构建的架构:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐
│ GitHub │──▶│ │ │ │ │ │ │ │
│ Stripe │──▶│ sun-port │──▶│ Hermes │──▶│ Agent run │──▶│ Delivery │
│ im-bot │──▶│ (auth, │ │ webhook │ │ (skills, │ │ (Telegram│
│ monitors │──▶│ TLS, RL) │ │ adapter │ │ prompt) │ │ etc.) │
└──────────┘ └──────────┘ └──────────┘ └───────────┘ └──────────┘
│
▼
┌──────────────────────────────┐
│ PostgreSQL 事件审计日志 │
└──────────────────────────────┘
在创建订阅之前,必须先启用 webhook 平台。先检查它的状态:
$ hermes webhook list
Webhook platform is not enabled. Run `hermes gateway setup`.
用 gateway 设置向导来启用它:
$ hermes gateway setup
? Enable webhooks? Yes
? Webhook port (8644): 8644
? Global HMAC secret: (generated)
或者直接在 ~/.hermes/config.yaml 里配置:
platforms:
webhook:
enabled: true
extra:
host: "0.0.0.0"
port: 8644
secret: "generate-a-strong-secret-here"
然后启动(或重启)gateway,并确认它在监听:
$ hermes gateway run
$ curl -s http://localhost:8644/health
{"status":"ok"}
/health 端点就是你的就绪检查。每个订阅把一个传入事件映射到一个 Agent 提示词。Hermes 用 {dot.notation} 占位符把 payload 渲染进提示词,触发一次 Agent 运行,并把结果交付到某个目标(Telegram、Discord、GitHub 评论,或事件来源本身)。
$ 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 and suggest a fix." \
--skills "github-issues" \
--deliver github_comment
✓ Subscription created
URL: https://agent-rule.com/webhook/github-issues
Secret: whsec_9f2a... (store this in GitHub)
在 GitHub 里,进入 Settings → Webhooks → Add webhook,设置 payload URL、content type application/json,以及返回的 secret。此后,GitHub 会在每一个 issue 事件上 POST 到那个 URL。
$ 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"
✓ Subscription created
URL: https://agent-rule.com/webhook/stripe-payments
$ hermes webhook subscribe alerts \
--events "alert" \
--prompt "Alert: {alert.name}\nSeverity: {alert.severity}\nMessage: {alert.message}\n\nPlease investigate and suggest remediation." \
--deliver origin
✓ Subscription created
URL: https://agent-rule.com/webhook/alerts
有时你根本不需要一次 Agent 运行——你只想把 payload 推送到一个聊天里。--deliver-only 标志会渲染提示词模板并原样转发,完全跳过 LLM 往返:
$ 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"
--deliver-only 在成功时返回 200、目标失败时返回 502——这样上游服务就能智能重试。HMAC 认证、速率限制和幂等性仍然适用。订阅持久化到 ~/.hermes/webhook_subscriptions.json,并由 adapter 热重载。用下面的命令管理它们:
$ hermes webhook list
$ hermes webhook test github-issues \
--payload '{"issue":{"number":42,"title":"Webhook test","user":{"login":"octocat"},"body":"Does this work?"},"action":"opened"}'
$ hermes webhook remove alerts
test 命令至关重要——它把一个合成的 payload 推过完整管线,这样你无需等待真实事件,就能验证提示词模板、HMAC 签名路径和交付目标。
绝不要直接暴露 webhook 端口。把它路由到 sun-port 之后,后者在 Hermes 前面提供 TLS 终止、HMAC 校验和逐路由的速率限制:
# sun-port/config.yaml
server:
listen: ":443"
tls:
cert: /etc/sun-port/certs/fullchain.pem
key: /etc/sun-port/certs/privkey.pem
routes:
- match:
host: "agent-rule.com"
path: "/webhook/*"
backend:
url: "http://hermes-agent:8644"
timeout: 60s
auth:
type: hmac_sha256
secret_file: /etc/sun-port/tokens/webhook-secret.key
rate_limit:
requests_per_minute: 30
- match:
host: "agent-rule.com"
path: "/api/*"
backend:
url: "http://hermes-agent:3000"
timeout: 30s
auth:
type: bearer
token_file: /etc/sun-port/tokens/hermes-api.key
rate_limit:
requests_per_minute: 60
重启后验证路由:
$ curl -s https://agent-rule.com/webhook/health
{"status":"ok"}
# 模拟一个带签名的 webhook(HMAC-SHA256)
$ printf '{"alert":{"name":"disk-full","severity":"critical","message":"/ is 98% full"}}' | \
openssl dgst -sha256 -hmac "$(cat /etc/sun-port/tokens/webhook-secret.key)" -binary | \
openssl base64
X3m9kQ... (use as X-Hub-Signature-256 header)
把 Hermes gateway(及其 webhook adapter)作为 Docker 服务运行,这样它就能在重启和升级后干净地存活下来:
version: "3.9"
services:
sun-port:
image: sun-port:latest
container_name: sun-port
restart: unless-stopped
ports:
- "443:443"
- "80:80"
volumes:
- ./sun-port/config.yaml:/etc/sun-port/config.yaml:ro
- ./certs:/etc/sun-port/certs:ro
depends_on:
- hermes-gateway
networks:
- agent-net
hermes-gateway:
image: hermes-agent:latest
container_name: hermes-gateway
restart: unless-stopped
expose:
- "8644"
- "3000"
volumes:
- ./hermes-profiles:/root/.hermes/profiles:ro
- ./hermes-config.yaml:/root/.hermes/config.yaml:ro
- ./webhook_subscriptions.json:/root/.hermes/webhook_subscriptions.json:ro
environment:
- WEBHOOK_ENABLED=true
- WEBHOOK_PORT=8644
- WEBHOOK_SECRET_FILE=/run/s...cret
secrets:
- webhook_secret
networks:
- agent-net
postgres:
image: postgres:16
container_name: agent-events-db
restart: unless-stopped
environment:
POSTGRES_DB: agent_events
POSTGRES_USER: agent_rule
POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
volumes:
- pgdata:/var/lib/postgresql/data
secrets:
- pg_password
networks:
- agent-net
networks:
agent-net:
driver: bridge
volumes:
pgdata:
secrets:
webhook_secret:
file: ./secrets/webhook_secret.txt
pg_password:
file: ./secrets/pg_password.txt
$ docker compose up -d
$ docker compose ps
NAME STATUS PORTS
sun-port Up 2 minutes 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
hermes-gateway Up 2 minutes 8644/tcp, 3000/tcp
agent-events-db Up 2 minutes 5432/tcp
webhook_subscriptions.json 以只读方式挂载,这样 adapter 能热重载订阅,同时又不允许容器覆盖你已版本化的配置。密钥放在 /run/secrets 里,绝不要放进环境变量。每一次 webhook 触发的运行都应被记录,这样你就能回答「什么被触发了、什么时候、Agent 又做了什么」。创建 schema:
-- schema.sql
CREATE TABLE IF NOT EXISTS webhook_events (
id BIGSERIAL PRIMARY KEY,
received_at TIMESTAMPTZ DEFAULT now(),
subscription TEXT NOT NULL,
source TEXT, -- 'github', 'stripe', 'im-bot', 'monitoring'
event_type TEXT,
payload JSONB, -- 完整传入 payload
signature_valid BOOLEAN,
run_triggered BOOLEAN DEFAULT false,
run_id TEXT, -- Hermes agent run id
delivery_target TEXT, -- 'telegram', 'github_comment', 'origin', ...
delivery_status TEXT, -- 'ok', 'failed'
latency_ms INTEGER
);
CREATE INDEX idx_events_received ON webhook_events(received_at DESC);
CREATE INDEX idx_events_subscription ON webhook_events(subscription);
CREATE INDEX idx_events_payload ON webhook_events USING GIN (payload);
-- 视图:最近 24 小时内的失败交付
CREATE VIEW recent_webhook_failures AS
SELECT subscription, event_type, received_at, delivery_status
FROM webhook_events
WHERE delivery_status = 'failed'
AND received_at > now() - INTERVAL '24 hours'
ORDER BY received_at DESC;
查询哪些订阅触发得最频繁——这有助于发现那些应当切换到 --deliver-only 的嘈杂来源:
SELECT subscription, COUNT(*) AS events,
SUM(CASE WHEN run_triggered THEN 1 ELSE 0 END) AS agent_runs,
ROUND(AVG(latency_ms)) AS avg_latency_ms
FROM webhook_events
WHERE received_at > now() - INTERVAL '7 days'
GROUP BY subscription
ORDER BY events DESC;
im-bot 是一个多 Agent 即时通讯平台,Agent 们在聊天室里共存。把 im-bot 接到 Hermes webhook 就闭合了这个循环:房间里的一条消息可以触发一次 Agent 运行,结果又投递回聊天里。
注册一个 im-bot connector,把消息事件 POST 到你的 webhook 端点,然后订阅:
$ hermes webhook subscribe im-bot-mentions \
--events "message.mention" \
--prompt "im-bot message from {message.sender_name} in room {room.name}:\n\n{message.text}\n\nRespond as the room agent." \
--skills "im-bot" \
--deliver im_bot \
--deliver-chat-id "{room.id}"
✓ Subscription created
URL: https://agent-rule.com/webhook/im-bot-mentions
流程是:用户在 im-bot 里 @提及 Agent → im-bot 把一个 message.mention 事件 POST 到 webhook → Hermes 带着房间上下文运行 Agent → 回复投递回 im-bot 房间。因为提示词模板把 {room.id} 插值进交付目标,一个订阅就能动态服务所有房间。
message.mention webhook,你就会陷入无限循环。加一个发送者过滤器(忽略来自 Agent 自己账号的消息),或在 im-bot connector 里加幂等键,让自我消息永远不会重新进入管线。订阅是配置,而配置应该进 Git。把 webhook_subscriptions.json 和 sun-port/config.yaml 放在一个仓库里,这样每一次变更都可审查、可回滚:
$ git init agent-events && cd agent-events
$ git add webhook_subscriptions.json sun-port/config.yaml docker-compose.yml schema.sql
$ git commit -m "Add GitHub issue triage and Stripe payment webhooks"
$ git log --oneline
a1b2c3d Add GitHub issue triage and Stripe payment webhooks
配合一个校验 JSON 的 pre-commit 钩子,你就能保证订阅文件在发布前总是可解析的——这正是我们在 CI/CD 教程里介绍的那套纪律。
hermes gateway setup,然后在创建订阅前确认 curl /health 返回 {"status":"ok"}。{dot.notation} 占位符把 payload 字段映射进 Agent 提示词;在信任真实事件之前,先用 hermes webhook test 测试它们。/run/secrets 里,绝不在环境变量里。Webhook 把 Hermes Agent 从一个调度器变成了一个响应式系统。GitHub 打开一个 issue、Stripe 结算一笔支付、监控器触发、用户在 im-bot 里 ping 你——Agent 已经开始工作了,由 sun-port 加固、由 PostgreSQL 追踪、由 Docker 发布。这就是「会检查」的 Agent 和「会知道」的 Agent 之间的区别。