Вебхуки

Принимайте события от внешних сервисов (GitHub, GitLab, JIRA, Stripe и т. д.) и автоматически запускайте прогоны Hermes Agent. Адаптер вебхуков поднимает HTTP-сервер, который принимает POST-запросы, проверяет HMAC-подписи, превращает полезную нагрузку в промпт для агента и направляет ответ обратно к источнику или на другую настроенную платформу.

Агент обрабатывает событие и в ответ может оставить комментарий в PR, отправить сообщение в Telegram/Discord или записать результат в лог.

Видеоурок


Быстрый старт

  1. Включите адаптер через hermes gateway setup или переменные окружения
  2. Опишите маршруты в config.yaml либо создайте их на лету командой hermes webhook subscribe
  3. Направьте свой сервис на 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

  1. Откройте репозиторий → SettingsWebhooksAdd webhook
  2. В Payload URL укажите http://your-server:8644/webhooks/github-pr
  3. В Content type выберите application/json
  4. В Secret впишите тот же секрет, что и в конфиге маршрута (например, github-webhook-secret)
  5. В блоке Which events? выберите Let me select individual events и отметьте Pull requests
  6. Нажмите 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

  1. Откройте проект → SettingsWebhooks
  2. В поле URL укажите http://your-server:8644/webhooks/gitlab-mr
  3. Впишите свой Secret token
  4. Отметьте Merge request events (и любые другие нужные события)
  5. Нажмите 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 UnauthorizedHMAC-подпись неверна или отсутствует.
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-секрет (используется как запасной, когда маршрут не задаёт свой)(нет)
ESC