Webhooks
외부 서비스(GitHub, GitLab, JIRA, Stripe 등)의 event를 받아 Hermes agent run을 자동으로 trigger할 수 있습니다. Webhook adapter는 POST request를 받는 HTTP server를 실행하고, HMAC signature를 검증하며, payload를 agent prompt로 변환하고, 응답을 원래 source나 다른 configured platform으로 route합니다.
Agent는 event를 처리한 뒤 PR에 comment를 남기거나, Telegram/Discord로 message를 보내거나, 결과를 log에 기록할 수 있습니다.
Video Tutorial
Quick Start
hermes gateway setup또는 environment variable로 webhook을 켭니다.config.yaml에 route를 정의하거나hermes webhook subscribe로 동적으로 만듭니다.- 서비스의 webhook URL을
http://your-server:8644/webhooks/<route-name>로 지정합니다.
Setup
Webhook adapter를 켜는 방법은 두 가지입니다.
Setup wizard 사용
hermes gateway setup
Prompt를 따라 webhooks를 enable하고, port와 global HMAC secret을 설정합니다.
Environment variables 사용
~/.hermes/.env에 추가합니다.
WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644 # default
WEBHOOK_SECRET=your-global-secret
Server 검증
Gateway가 실행 중이면 다음을 호출합니다.
curl http://localhost:8644/health
기대 응답:
{"status": "ok", "platform": "webhook"}
Configuring Routes
Route는 서로 다른 webhook source를 어떻게 처리할지 정의합니다. 각 route는 config.yaml의 platforms.webhook.extra.routes 아래에 이름이 붙은 entry로 들어갑니다.
Route properties
| Property | Required | Description |
|---|---|---|
events | No | 받을 event type list입니다. 예: ["pull_request"]. 비어 있으면 모든 event를 받습니다. Event type은 X-GitHub-Event, X-GitLab-Event, 또는 payload의 event_type에서 읽습니다. |
secret | Yes | Signature validation용 HMAC secret입니다. Route에 없으면 global secret으로 fallback합니다. 테스트에서만 "INSECURE_NO_AUTH"로 설정해 validation을 건너뛸 수 있습니다. |
prompt | No | Dot-notation payload access를 사용하는 template string입니다. 예: {pull_request.title}. 생략하면 전체 JSON payload가 prompt에 dump됩니다. |
skills | No | Agent run에 load할 skill name list입니다. |
deliver | No | Response를 보낼 위치입니다. github_comment, telegram, discord, slack, signal, sms, whatsapp, matrix, mattermost, homeassistant, email, dingtalk, feishu, wecom, weixin, bluebubbles, qqbot, 또는 log(기본값)를 사용할 수 있습니다. |
deliver_extra | No | 추가 delivery config입니다. Key는 deliver type에 따라 다릅니다. 예: repo, pr_number, chat_id. 값은 prompt와 같은 {dot.notation} template을 지원합니다. |
deliver_only | No | true이면 agent를 완전히 건너뜁니다. Rendered prompt template이 그대로 delivered message가 됩니다. LLM 비용은 0이고 sub-second delivery가 가능합니다. 사용 사례는 Direct Delivery Mode를 참고하세요. deliver가 실제 target이어야 하며 log는 허용되지 않습니다. |
Full example
platforms:
webhook:
enabled: true
extra:
port: 8644
secret: "global-fallback-secret"
routes:
github-pr:
events: ["pull_request"]
secret: "github-webhook-secret"
prompt: |
Review this pull request:
Repository: {repository.full_name}
PR #{number}: {pull_request.title}
Author: {pull_request.user.login}
URL: {pull_request.html_url}
Diff URL: {pull_request.diff_url}
Action: {action}
skills: ["github-code-review"]
deliver: "github_comment"
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{number}"
deploy-notify:
events: ["push"]
secret: "deploy-secret"
prompt: "New push to {repository.full_name} branch {ref}: {head_commit.message}"
deliver: "telegram"
Prompt Templates
Prompt는 webhook payload의 nested field에 접근하기 위해 dot-notation을 사용합니다.
{pull_request.title}은payload["pull_request"]["title"]로 resolve됩니다.{repository.full_name}은payload["repository"]["full_name"]으로 resolve됩니다.{__raw__}는 전체 payload를 indented JSON으로 dump하는 special token입니다. 4000자로 truncate됩니다. Monitoring alert나 generic webhook처럼 agent가 전체 context를 봐야 할 때 유용합니다.- Missing key는 error가 아니라 literal
{key}string으로 남습니다. - Nested dict와 list는 JSON으로 serialize되고 2000자로 truncate됩니다.
{__raw__}와 일반 template variable을 섞어 쓸 수 있습니다.
prompt: "PR #{pull_request.number} by {pull_request.user.login}: {__raw__}"
Route에 prompt template이 없으면 전체 payload가 indented JSON으로 dump됩니다. 이때도 4000자로 truncate됩니다.
같은 dot-notation template은 deliver_extra 값에서도 동작합니다.
Forum Topic Delivery
Webhook response를 Telegram으로 보낼 때 deliver_extra에 message_thread_id 또는 thread_id를 넣으면 특정 forum topic을 target할 수 있습니다.
webhooks:
routes:
alerts:
events: ["alert"]
prompt: "Alert: {__raw__}"
deliver: "telegram"
deliver_extra:
chat_id: "-1001234567890"
message_thread_id: "42"
deliver_extra에 chat_id가 없으면 delivery는 target platform에 설정된 home channel로 fallback합니다.
GitHub PR Review(step by step)
이 walkthrough는 모든 pull request에 대해 자동 code review를 설정합니다.
1. GitHub에서 webhook 만들기
- Repository에서 Settings -> Webhooks -> Add webhook으로 이동합니다.
- Payload URL을
http://your-server:8644/webhooks/github-pr로 설정합니다. - Content type을
application/json으로 설정합니다. - Secret을 route config와 같은 값으로 설정합니다. 예:
github-webhook-secret. - **Which events?**에서 Let me select individual events를 선택하고 Pull requests를 체크합니다.
- Add webhook을 클릭합니다.
2. Route config 추가
위 full example처럼 github-pr route를 ~/.hermes/config.yaml에 추가합니다.
3. gh CLI 인증 확인
github_comment delivery type은 GitHub CLI로 comment를 게시합니다.
gh auth login
4. 테스트
해당 repository에 pull request를 엽니다. Webhook이 fire되고, Hermes가 event를 처리한 뒤 PR에 review comment를 게시합니다.
GitLab Webhook Setup
GitLab webhook도 비슷하게 동작하지만 인증 방식이 다릅니다. GitLab은 secret을 plain X-Gitlab-Token header로 보냅니다. HMAC이 아니라 exact string match입니다.
1. GitLab에서 webhook 만들기
- Project에서 Settings -> Webhooks로 이동합니다.
- URL을
http://your-server:8644/webhooks/gitlab-mr로 설정합니다. - Secret token을 입력합니다.
- Merge request events와 필요한 다른 event를 선택합니다.
- Add webhook을 클릭합니다.
2. Route config 추가
platforms:
webhook:
enabled: true
extra:
routes:
gitlab-mr:
events: ["merge_request"]
secret: "your-gitlab-secret-token"
prompt: |
Review this merge request:
Project: {project.path_with_namespace}
MR !{object_attributes.iid}: {object_attributes.title}
Author: {object_attributes.last_commit.author.name}
URL: {object_attributes.url}
Action: {object_attributes.action}
deliver: "log"
Delivery Options
deliver field는 webhook event를 처리한 뒤 agent response가 어디로 갈지 결정합니다.
| Deliver Type | Description |
|---|---|
log | Gateway log output에 response를 기록합니다. 기본값이며 testing에 유용합니다. |
github_comment | gh CLI로 PR/issue comment를 게시합니다. deliver_extra.repo와 deliver_extra.pr_number가 필요합니다. Gateway host에 gh CLI가 설치되어 있고 인증되어 있어야 합니다(gh auth login). |
telegram | Response를 Telegram으로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
discord | Response를 Discord로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
slack | Response를 Slack으로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
signal | Response를 Signal로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
sms | Twilio를 통해 response를 SMS로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
whatsapp | Response를 WhatsApp으로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
matrix | Response를 Matrix로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
mattermost | Response를 Mattermost로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
homeassistant | Response를 Home Assistant로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
email | Response를 Email로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
dingtalk | Response를 DingTalk로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
feishu | Response를 Feishu/Lark로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
wecom | Response를 WeCom으로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
weixin | Response를 Weixin(WeChat)으로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
bluebubbles | Response를 BlueBubbles(iMessage)로 route합니다. Home channel을 사용하거나 deliver_extra에 chat_id를 지정합니다. |
Cross-platform delivery를 사용하려면 target platform도 gateway에서 enabled 및 connected 상태여야 합니다. deliver_extra에 chat_id가 없으면 response는 해당 platform의 configured home channel로 전송됩니다.
Direct Delivery Mode
기본적으로 webhook POST는 agent run을 trigger합니다. Payload가 prompt가 되고, agent가 이를 처리한 뒤 agent response가 delivered됩니다. 이 방식은 event마다 LLM token을 사용합니다.
그냥 알림을 push하고 싶은 경우, 즉 reasoning이나 agent loop 없이 message만 전달하려면 route에 deliver_only: true를 설정하세요. Rendered prompt template이 literal message body가 되고, adapter가 configured delivery target으로 직접 dispatch합니다.
Direct delivery를 쓰기 좋은 경우
- External service push - Supabase/Firebase webhook이 database change에서 fire되고 Telegram으로 즉시 사용자에게 알림
- Monitoring alerts - Datadog/Grafana alert webhook을 Discord channel로 push
- Inter-agent pings - Agent A가 long-running task 완료를 Agent B의 user에게 알림
- Background job completion - Cron job 완료 결과를 Slack으로 게시
장점:
- LLM token 0 - agent가 호출되지 않습니다.
- Sub-second delivery - reasoning loop 없이 adapter call 하나만 수행합니다.
- Agent mode와 같은 security - HMAC auth, rate limit, idempotency, body-size limit이 모두 그대로 적용됩니다.
- Synchronous response - Delivery가 성공하면 POST가
200 OK를 반환하고, target이 거부하면502를 반환하므로 upstream service가 retry를 판단할 수 있습니다.
예: Supabase에서 Telegram push
platforms:
webhook:
enabled: true
extra:
port: 8644
secret: "global-secret"
routes:
antenna-matches:
secret: "antenna-webhook-secret"
deliver: "telegram"
deliver_only: true
prompt: "New match: {match.user_name} matched with you!"
deliver_extra:
chat_id: "{match.telegram_chat_id}"
Supabase edge function이 HMAC-SHA256으로 payload에 sign하고 https://your-server:8644/webhooks/antenna-matches에 POST합니다. Webhook adapter는 signature를 검증하고, payload로 template을 render하고, Telegram으로 전달한 뒤 200 OK를 반환합니다.
예: CLI로 dynamic subscription 만들기
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"
Response codes
| Status | Meaning |
|---|---|
200 OK | Delivery 성공. Body: {"status": "delivered", "route": "...", "target": "...", "delivery_id": "..."} |
200 OK(status=duplicate) | Idempotency TTL(1시간) 안에 같은 X-GitHub-Delivery ID가 다시 들어왔습니다. 다시 deliver하지 않습니다. |
401 Unauthorized | HMAC signature가 invalid 또는 missing입니다. |
400 Bad Request | JSON body가 malformed입니다. |
404 Not Found | 알 수 없는 route name입니다. |
413 Payload Too Large | Body가 max_body_bytes를 초과했습니다. |
429 Too Many Requests | Route rate limit을 초과했습니다. |
502 Bad Gateway | Target adapter가 message를 거부했거나 exception을 냈습니다. Error는 server-side log에 남고, response body는 adapter internals 유출을 피하기 위해 generic Delivery failed입니다. |
Configuration gotchas
deliver_only: true에는 실제delivertarget이 필요합니다.deliver: log또는deliver생략은 startup에서 거부됩니다. Adapter는 misconfigured route를 발견하면 시작하지 않습니다.- Direct delivery mode에서는
skillsfield가 무시됩니다. Agent run이 없으므로 주입할 skill도 없습니다. - Template rendering은 agent mode와 같은
{dot.notation}syntax를 사용하며{__raw__}token도 포함됩니다. - Idempotency는 같은
X-GitHub-Delivery/X-Request-IDheader를 사용합니다. 같은 ID로 retry하면status=duplicate를 반환하고 다시 deliver하지 않습니다.
Dynamic Subscriptions(CLI)
config.yaml의 static route에 더해, hermes webhook CLI command로 webhook subscription을 동적으로 만들 수 있습니다. Agent 자신이 event-driven trigger를 설정해야 할 때 특히 유용합니다.
Subscription 생성
hermes webhook subscribe github-issues \
--events "issues" \
--prompt "New issue #{issue.number}: {issue.title}\nBy: {issue.user.login}\n\n{issue.body}" \
--deliver telegram \
--deliver-chat-id "-100123456789" \
--description "Triage new GitHub issues"
이 명령은 webhook URL과 auto-generated HMAC secret을 반환합니다. Service가 그 URL로 POST하도록 설정하세요.
Subscription 목록 보기
hermes webhook list
Subscription 제거
hermes webhook remove github-issues
Subscription 테스트
hermes webhook test github-issues
hermes webhook test github-issues --payload '{"issue": {"number": 42, "title": "Test"}}'
Dynamic subscription 동작 방식
- Subscription은
~/.hermes/webhook_subscriptions.json에 저장됩니다. - Webhook adapter는 incoming request마다 이 파일을 hot-reload합니다. mtime-gated라 overhead는 무시할 수 있습니다.
config.yaml의 static route는 같은 이름의 dynamic route보다 항상 우선합니다.- Dynamic subscription은 static route와 같은 route format과 capability를 사용합니다. Events, prompt template, skills, delivery가 모두 같습니다.
- Gateway restart가 필요 없습니다. Subscribe하면 즉시 live 상태가 됩니다.
Agent-driven subscriptions
Agent는 webhook-subscriptions skill의 안내를 받아 terminal tool로 subscription을 만들 수 있습니다. Agent에게 "set up a webhook for GitHub issues"라고 요청하면 적절한 hermes webhook subscribe command를 실행합니다.
Security
Webhook adapter에는 여러 보안 계층이 포함되어 있습니다.
HMAC signature validation
Adapter는 source별로 적절한 방식으로 incoming webhook signature를 검증합니다.
- GitHub:
X-Hub-Signature-256header.sha256=prefix가 붙은 HMAC-SHA256 hex digest입니다. - GitLab:
X-Gitlab-Tokenheader. Plain secret string match입니다. - Generic:
X-Webhook-Signatureheader. Raw HMAC-SHA256 hex digest입니다.
Secret이 설정되어 있는데 인식 가능한 signature header가 없으면 request는 거부됩니다.
Secret is required
모든 route에는 secret이 필요합니다. Route에 직접 설정하거나 global secret에서 상속해야 합니다. Secret이 없는 route가 있으면 adapter는 startup에서 error로 실패합니다. Development/testing에서만 secret을 "INSECURE_NO_AUTH"로 설정해 validation을 완전히 건너뛸 수 있습니다.
INSECURE_NO_AUTH는 gateway가 loopback host(127.0.0.1, localhost, ::1)에 bind되어 있을 때만 허용됩니다. 0.0.0.0이나 LAN IP 같은 non-loopback bind와 함께 쓰면 adapter가 시작을 거부합니다. Public interface에 unauthenticated endpoint를 실수로 노출하지 않기 위한 장치입니다.
Rate limiting
각 route는 기본적으로 분당 30 request로 rate-limit됩니다(fixed-window). Global 설정은 다음과 같습니다.
platforms:
webhook:
extra:
rate_limit: 60 # requests per minute
Limit을 넘은 request는 429 Too Many Requests를 받습니다.
Idempotency
Delivery ID(X-GitHub-Delivery, X-Request-ID, 또는 timestamp fallback)는 1시간 cache됩니다. Webhook retry 같은 duplicate delivery는 200 response와 함께 조용히 skip되어 duplicate agent run을 막습니다.
Body size limits
1 MB를 초과하는 payload는 body를 읽기 전에 거부됩니다. 설정 예:
platforms:
webhook:
extra:
max_body_bytes: 2097152 # 2 MB
Prompt injection risk
Webhook payload에는 attacker-controlled data가 들어 있습니다. PR title, commit message, issue description 등이 모두 malicious instruction을 포함할 수 있습니다. Internet에 노출한다면 gateway를 sandboxed environment(Docker, VM)에서 실행하세요. 격리를 위해 Docker 또는 SSH terminal backend 사용도 고려하세요.
Troubleshooting
Webhook이 도착하지 않음
- Port가 webhook source에서 접근 가능하게 노출되어 있는지 확인하세요.
- Firewall rule을 확인하세요.
8644또는 설정한 port가 열려 있어야 합니다. - URL path가 맞는지 확인하세요:
http://your-server:8644/webhooks/<route-name> /healthendpoint로 server가 실행 중인지 확인하세요.
Signature validation 실패
- Route config의 secret이 webhook source에 설정한 secret과 정확히 같은지 확인하세요.
- GitHub는 HMAC 기반입니다.
X-Hub-Signature-256을 확인하세요. - GitLab은 plain token match입니다.
X-Gitlab-Token을 확인하세요. - Gateway log에서
Invalid signaturewarning을 확인하세요.
Event가 무시됨
- Event type이 route의
eventslist에 들어 있는지 확인하세요. - GitHub event는
pull_request,push,issues같은 값을 사용합니다. 이는X-GitHub-Eventheader value입니다. - GitLab event는
merge_request,push같은 값을 사용합니다. 이는X-GitLab-Eventheader value입니다. events가 비어 있거나 설정되지 않았다면 모든 event를 받습니다.
Agent가 응답하지 않음
- Log를 보기 위해 gateway를 foreground로 실행하세요:
hermes gateway run - Prompt template이 올바르게 render되는지 확인하세요.
- Delivery target이 설정되어 있고 connected 상태인지 확인하세요.
Duplicate responses
- Idempotency cache가 이를 막아야 합니다. Webhook source가 delivery ID header(
X-GitHub-Delivery또는X-Request-ID)를 보내는지 확인하세요. - Delivery ID는 1시간 동안 cache됩니다.
gh CLI 오류(GitHub comment delivery)
- Gateway host에서
gh auth login을 실행하세요. - 인증된 GitHub user가 repository에 write access를 가지고 있는지 확인하세요.
gh가 설치되어 있고 PATH에 있는지 확인하세요.
Environment Variables
| Variable | Description | Default |
|---|---|---|
WEBHOOK_ENABLED | Webhook platform adapter 활성화 | false |
WEBHOOK_PORT | Webhook을 받는 HTTP server port | 8644 |
WEBHOOK_SECRET | Global HMAC secret. Route에 자체 secret이 없을 때 fallback으로 사용됩니다. | (none) |