Вебхуки
Принимайте события от внешних сервисов (GitHub, GitLab, JIRA, Stripe и т. д.) и автоматически запускайте прогоны Hermes Agent. Адаптер вебхуков поднимает HTTP-сервер, который принимает POST-запросы, проверяет HMAC-подписи, превращает полезную нагрузку в промпт для агента и направляет ответ обратно к источнику или на другую настроенную платформу.
Агент обрабатывает событие и в ответ может оставить комментарий в PR, отправить сообщение в Telegram/Discord или записать результат в лог.
Видеоурок
Быстрый старт
- Включите адаптер через
hermes gateway setupили переменные окружения - Опишите маршруты в
config.yamlлибо создайте их на лету командойhermes webhook subscribe - Направьте свой сервис на
http://your-server:8644/webhooks/<route-name>
Настройка
Включить адаптер вебхуков можно двумя способами.
Через мастер настройки
hermes gateway setup
Дальше отвечайте на вопросы мастера: включите вебхуки, задайте порт и общий HMAC-секрет.
Через переменные окружения
Добавьте в ~/.hermes/.env:
WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644 # default
WEBHOOK_SECRET=your-global-secret
Проверка сервера
Когда шлюз запущен:
curl http://localhost:8644/health
Ожидаемый ответ:
{"status": "ok", "platform": "webhook"}
Настройка маршрутов
Маршруты задают, как обрабатывать события из разных источников. Каждый маршрут — это именованная запись в секции platforms.webhook.extra.routes вашего config.yaml.
Свойства маршрута
| Свойство | Обязательно | Описание |
|---|---|---|
events | Нет | Список типов событий, которые принимать (например, ["pull_request"]). Если пусто — принимаются все события. Тип события читается из заголовков X-GitHub-Event, X-GitLab-Event или из поля event_type в полезной нагрузке. |
secret | Да | HMAC-секрет для проверки подписи. Если на маршруте не задан, подхватывается глобальный secret. Значение "INSECURE_NO_AUTH" отключает проверку — только для тестов. |
prompt | Нет | Строка-шаблон с доступом к полям через точечную нотацию (например, {pull_request.title}). Если опущена, в промпт сбрасывается вся полезная нагрузка в формате JSON. |
skills | Нет | Список имён навыков, которые загрузить для прогона агента. |
deliver | Нет | Куда отправить ответ: github_comment, telegram, discord, slack, signal, sms, whatsapp, matrix, mattermost, homeassistant, email, dingtalk, feishu, wecom, weixin, bluebubbles, qqbot или log (по умолчанию). |
deliver_extra | Нет | Дополнительная конфигурация доставки — набор ключей зависит от типа deliver (например, repo, pr_number, chat_id). Значения поддерживают те же шаблоны {dot.notation}, что и prompt. |
deliver_only | Нет | Если true, агент пропускается целиком — отрендеренный шаблон prompt становится буквальным сообщением, которое и доставляется. Ноль расходов на LLM, доставка меньше чем за секунду. Сценарии см. в разделе Режим прямой доставки. Требует, чтобы deliver указывал на реальную цель (не log). |
Полный пример
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"
Шаблоны промптов
В промптах через точечную нотацию вытаскиваются вложенные поля из полезной нагрузки вебхука:
{pull_request.title}разворачивается вpayload["pull_request"]["title"]{repository.full_name}разворачивается вpayload["repository"]["full_name"]{__raw__}— особый токен, который сбрасывает всю полезную нагрузку как JSON с отступами (обрезается до 4000 символов). Удобно для алертов мониторинга или общих вебхуков, когда агенту нужен полный контекст.- Отсутствующие ключи остаются буквальной строкой
{key}(без ошибки) - Вложенные словари и списки сериализуются в JSON и обрезаются до 2000 символов
{__raw__} можно смешивать с обычными переменными шаблона:
prompt: "PR #{pull_request.number} by {pull_request.user.login}: {__raw__}"
Если для маршрута шаблон prompt не задан, вся полезная нагрузка сбрасывается как JSON с отступами (обрезается до 4000 символов).
Те же шаблоны с точечной нотацией работают и в значениях deliver_extra.
Доставка в тему форума
При доставке ответов вебхука в Telegram можно адресовать конкретную тему форума — для этого добавьте в deliver_extra поле message_thread_id (или thread_id):
webhooks:
routes:
alerts:
events: ["alert"]
prompt: "Alert: {__raw__}"
deliver: "telegram"
deliver_extra:
chat_id: "-1001234567890"
message_thread_id: "42"
Если chat_id в deliver_extra не указан, доставка уходит в домашний канал, настроенный для целевой платформы.
Ревью PR на GitHub (по шагам)
Это руководство настраивает автоматическое ревью кода на каждый pull request.
1. Создайте вебхук в GitHub
- Откройте репозиторий → Settings → Webhooks → Add webhook
- В Payload URL укажите
http://your-server:8644/webhooks/github-pr - В Content type выберите
application/json - В Secret впишите тот же секрет, что и в конфиге маршрута (например,
github-webhook-secret) - В блоке Which events? выберите Let me select individual events и отметьте Pull requests
- Нажмите Add webhook
2. Добавьте конфиг маршрута
Добавьте маршрут github-pr в свой ~/.hermes/config.yaml, как показано в примере выше.
3. Убедитесь, что CLI gh авторизован
Тип доставки github_comment использует GitHub CLI, чтобы публиковать комментарии:
gh auth login
4. Проверьте работу
Откройте pull request в репозитории. Сработает вебхук, Hermes обработает событие и оставит на PR комментарий с ревью.
Настройка вебхука GitLab
Вебхуки GitLab устроены похоже, но используют другой механизм аутентификации. GitLab передаёт секрет открытым заголовком X-Gitlab-Token (точное совпадение строки, а не HMAC).
1. Создайте вебхук в GitLab
- Откройте проект → Settings → Webhooks
- В поле URL укажите
http://your-server:8644/webhooks/gitlab-mr - Впишите свой Secret token
- Отметьте Merge request events (и любые другие нужные события)
- Нажмите Add webhook
2. Добавьте конфиг маршрута
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"
Варианты доставки
Поле deliver определяет, куда уйдёт ответ агента после обработки события вебхука.
| Тип доставки | Описание |
|---|---|
log | Пишет ответ в лог шлюза. Значение по умолчанию, удобно для тестов. |
github_comment | Публикует ответ комментарием к PR/issue через CLI gh. Требует deliver_extra.repo и deliver_extra.pr_number. CLI gh должен быть установлен и авторизован на хосте шлюза (gh auth login). |
telegram | Отправляет ответ в Telegram. Использует домашний канал либо chat_id, указанный в deliver_extra. |
discord | Отправляет ответ в Discord. Использует домашний канал либо chat_id, указанный в deliver_extra. |
slack | Отправляет ответ в Slack. Использует домашний канал либо chat_id, указанный в deliver_extra. |
signal | Отправляет ответ в Signal. Использует домашний канал либо chat_id, указанный в deliver_extra. |
sms | Отправляет ответ по SMS через Twilio. Использует домашний канал либо chat_id, указанный в deliver_extra. |
whatsapp | Отправляет ответ в WhatsApp. Использует домашний канал либо chat_id, указанный в deliver_extra. |
matrix | Отправляет ответ в Matrix. Использует домашний канал либо chat_id, указанный в deliver_extra. |
mattermost | Отправляет ответ в Mattermost. Использует домашний канал либо chat_id, указанный в deliver_extra. |
homeassistant | Отправляет ответ в Home Assistant. Использует домашний канал либо chat_id, указанный в deliver_extra. |
email | Отправляет ответ по электронной почте. Использует домашний канал либо chat_id, указанный в deliver_extra. |
dingtalk | Отправляет ответ в DingTalk. Использует домашний канал либо chat_id, указанный в deliver_extra. |
feishu | Отправляет ответ в Feishu/Lark. Использует домашний канал либо chat_id, указанный в deliver_extra. |
wecom | Отправляет ответ в WeCom. Использует домашний канал либо chat_id, указанный в deliver_extra. |
weixin | Отправляет ответ в Weixin (WeChat). Использует домашний канал либо chat_id, указанный в deliver_extra. |
bluebubbles | Отправляет ответ в BlueBubbles (iMessage). Использует домашний канал либо chat_id, указанный в deliver_extra. |
Для доставки на другую платформу эта целевая платформа тоже должна быть включена и подключена в шлюзе. Если chat_id в deliver_extra не задан, ответ уходит в домашний канал, настроенный для этой платформы.
Режим прямой доставки
По умолчанию каждый POST на вебхук запускает прогон агента: полезная нагрузка превращается в промпт, агент её обрабатывает, и его ответ доставляется адресату. Каждое событие тратит токены LLM.
Но бывает, что нужно просто протолкнуть короткое уведомление — без рассуждений, без цикла агента, только доставить сообщение. Тогда поставьте на маршруте deliver_only: true. Отрендеренный шаблон prompt становится буквальным телом сообщения, и адаптер сразу отправляет его на настроенную цель доставки.
Когда стоит выбрать прямую доставку
- Пуш от внешнего сервиса — вебхук Supabase/Firebase сработал при изменении в базе → мгновенно уведомляем пользователя в Telegram
- Алерты мониторинга — вебхук тревоги Datadog/Grafana → пуш в канал Discord
- Пинги между агентами — агент A сообщает пользователю агента B, что долгая задача завершилась
- Завершение фоновой задачи — задание cron отработало → результат в Slack
Что это даёт:
- Ноль токенов LLM — агент вообще не вызывается
- Доставка меньше чем за секунду — один вызов адаптера, без цикла рассуждений
- Та же защита, что и в режиме агента — HMAC-аутентификация, лимиты частоты, идемпотентность и ограничение размера тела по-прежнему действуют
- Синхронный ответ — POST возвращает
200 OK, как только доставка прошла, или502, если цель её отвергла, так что ваш сервис-отправитель может разумно повторить попытку
Пример: пуш в Telegram из Supabase
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}"
Edge-функция Supabase подписывает полезную нагрузку через HMAC-SHA256 и шлёт POST на https://your-server:8644/webhooks/antenna-matches. Адаптер вебхуков проверяет подпись, рендерит шаблон по полезной нагрузке, доставляет сообщение в Telegram и возвращает 200 OK.
Пример: динамическая подписка через CLI
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"
Коды ответов
| Статус | Значение |
|---|---|
200 OK | Успешно доставлено. Тело: {"status": "delivered", "route": "...", "target": "...", "delivery_id": "..."} |
200 OK (status=duplicate) | Дублирующийся ID X-GitHub-Delivery в пределах TTL идемпотентности (1 час). Повторно не доставляется. |
401 Unauthorized | HMAC-подпись неверна или отсутствует. |
400 Bad Request | Некорректное тело JSON. |
404 Not Found | Неизвестное имя маршрута. |
413 Payload Too Large | Тело превысило max_body_bytes. |
429 Too Many Requests | Превышен лимит частоты для маршрута. |
502 Bad Gateway | Целевой адаптер отверг сообщение или выбросил исключение. Ошибка пишется в лог на стороне сервера; в теле ответа возвращается обобщённое Delivery failed, чтобы не раскрывать внутренности адаптера. |
Подводные камни конфигурации
deliver_only: trueтребует, чтобыdeliverуказывал на реальную цель. Вариантdeliver: log(как и опущенныйdeliver) отклоняется при старте — адаптер откажется запускаться, если найдёт неправильно настроенный маршрут.- Поле
skillsв режиме прямой доставки игнорируется (прогона агента нет, поэтому навыки некуда внедрять). - Рендеринг шаблона использует тот же синтаксис
{dot.notation}, что и режим агента, включая токен{__raw__}. - Идемпотентность опирается на тот же заголовок
X-GitHub-Delivery/X-Request-ID— повторы с тем же ID возвращаютstatus=duplicateи НЕ доставляются заново.
Динамические подписки (CLI)
Помимо статических маршрутов в config.yaml, подписки на вебхуки можно создавать на лету командой hermes webhook. Особенно удобно, когда триггеры на события должен настроить сам агент.
Создание подписки
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"
Команда возвращает URL вебхука и автоматически сгенерированный HMAC-секрет. Настройте свой сервис на POST по этому URL.
Список подписок
hermes webhook list
Удаление подписки
hermes webhook remove github-issues
Проверка подписки
hermes webhook test github-issues
hermes webhook test github-issues --payload '{"issue": {"number": 42, "title": "Test"}}'
Как устроены динамические подписки
- Подписки хранятся в
~/.hermes/webhook_subscriptions.json - Адаптер вебхуков перечитывает этот файл на каждый входящий запрос (по mtime, накладные расходы ничтожны)
- Статические маршруты из
config.yamlвсегда имеют приоритет над динамическими с тем же именем - Динамические подписки используют тот же формат и возможности маршрутов, что и статические (события, шаблоны промптов, навыки, доставка)
- Перезапуск шлюза не нужен — подписался, и маршрут сразу в строю
Подписки от лица агента
Агент умеет создавать подписки через терминальный инструмент, если им управляет навык webhook-subscriptions. Попросите агента «настроить вебхук для GitHub issues» — и он сам выполнит подходящую команду hermes webhook subscribe.
Безопасность
В адаптере вебхуков несколько слоёв защиты.
Проверка HMAC-подписи
Адаптер проверяет подписи входящих вебхуков методом, подходящим для каждого источника:
- GitHub: заголовок
X-Hub-Signature-256— hex-дайджест HMAC-SHA256 с префиксомsha256= - GitLab: заголовок
X-Gitlab-Token— совпадение открытой строки секрета - Generic: заголовок
X-Webhook-Signature— чистый hex-дайджест HMAC-SHA256
Если секрет настроен, но ни одного распознаваемого заголовка с подписью нет, запрос отклоняется.
Секрет обязателен
У каждого маршрута должен быть секрет — заданный прямо на маршруте либо унаследованный от глобального secret. Маршруты без секрета приводят к падению адаптера при старте с ошибкой. Только для разработки и тестов секрет можно поставить в "INSECURE_NO_AUTH", чтобы полностью пропустить проверку.
INSECURE_NO_AUTH принимается, только когда шлюз привязан к loopback-хосту (127.0.0.1, localhost, ::1). Если же его сочетать с не-loopback-привязкой вроде 0.0.0.0 или LAN-адреса, адаптер откажется стартовать — так исключается случайная публикация эндпоинта без аутентификации на публичном интерфейсе.
Ограничение частоты запросов
По умолчанию каждый маршрут ограничен 30 запросами в минуту (фиксированное окно). Настраивается глобально:
platforms:
webhook:
extra:
rate_limit: 60 # requests per minute
Запросы сверх лимита получают ответ 429 Too Many Requests.
Идемпотентность
ID доставки (из X-GitHub-Delivery, X-Request-ID или резервной метки времени) кэшируются на 1 час. Дублирующиеся доставки (например, повторы вебхука) молча пропускаются с ответом 200, так что лишних прогонов агента не происходит.
Ограничение размера тела
Полезные нагрузки больше 1 МБ отклоняются ещё до чтения тела. Настраивается:
platforms:
webhook:
extra:
max_body_bytes: 2097152 # 2 MB
Риск инъекции в промпт
В полезной нагрузке вебхука лежат данные, которыми управляет атакующий: заголовки PR, сообщения коммитов, описания issue — всё это может содержать вредоносные инструкции. Когда шлюз открыт в интернет, запускайте его в песочнице (Docker, VM). Для изоляции стоит рассмотреть терминальный бэкенд на Docker или SSH.
Устранение неполадок
Вебхук не приходит
- Проверьте, что порт открыт и доступен со стороны источника вебхука
- Проверьте правила фаервола — порт
8644(или ваш настроенный) должен быть открыт - Сверьте путь в URL:
http://your-server:8644/webhooks/<route-name> - Подтвердите, что сервер работает, через эндпоинт
/health
Не проходит проверка подписи
- Убедитесь, что секрет в конфиге маршрута точно совпадает с секретом, заданным в источнике вебхука
- Для GitHub секрет основан на HMAC — проверьте
X-Hub-Signature-256 - Для GitLab секрет — совпадение открытого токена — проверьте
X-Gitlab-Token - Поищите в логах шлюза предупреждения
Invalid signature
Событие игнорируется
- Проверьте, что тип события есть в списке
eventsмаршрута - События GitHub приходят со значениями вроде
pull_request,push,issues(значение заголовкаX-GitHub-Event) - События GitLab приходят со значениями вроде
merge_request,push(значение заголовкаX-GitLab-Event) - Если
eventsпуст или не задан, принимаются все события
Агент не отвечает
- Запустите шлюз на переднем плане, чтобы видеть логи:
hermes gateway run - Проверьте, что шаблон промпта рендерится правильно
- Убедитесь, что цель доставки настроена и подключена
Дублирующиеся ответы
- Кэш идемпотентности должен это предотвращать — проверьте, что источник вебхука присылает заголовок с ID доставки (
X-GitHub-DeliveryилиX-Request-ID) - ID доставки кэшируются на 1 час
Ошибки CLI gh (доставка комментариев в GitHub)
- Выполните
gh auth loginна хосте шлюза - Убедитесь, что у авторизованного пользователя GitHub есть права на запись в репозиторий
- Проверьте, что
ghустановлен и есть в PATH
Переменные окружения
| Переменная | Описание | По умолчанию |
|---|---|---|
WEBHOOK_ENABLED | Включает адаптер платформы вебхуков | false |
WEBHOOK_PORT | Порт HTTP-сервера для приёма вебхуков | 8644 |
WEBHOOK_SECRET | Глобальный HMAC-секрет (используется как запасной, когда маршрут не задаёт свой) | (нет) |