Pular para o conteúdo principal

Documentação oficial desta página · v0.20.6

Webhooks

Receba eventos de serviços externos (GitHub, GitLab, JIRA, Stripe etc.) e dispare execuções do agente Hermes automaticamente. O adaptador de webhook roda um servidor HTTP que aceita requisições POST, valida assinaturas HMAC, transforma payloads em prompts para o agente e devolve as respostas para a origem ou para outra plataforma configurada.

O agente processa o evento e pode responder publicando comentários em PRs, mandando mensagens para o Telegram/Discord ou registrando o resultado no log.

Tutorial em vídeo​


Início rápido​

  1. Ative pelo hermes gateway setup ou por variáveis de ambiente
  2. Defina rotas no config.yaml ou crie rotas dinamicamente com hermes webhook subscribe
  3. Aponte seu serviço para http://seu-servidor:8644/webhooks/<nome-da-rota>

Configuração inicial​

Há duas formas de ativar o adaptador de webhook.

Pelo assistente de setup​

hermes gateway setup

Siga as perguntas para ativar os webhooks, definir a porta e definir um segredo HMAC global.

Por variáveis de ambiente​

Adicione ao ~/.hermes/.env:

WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644 # padrão
WEBHOOK_SECRET=seu-segredo-global

Verifique o servidor​

Com o gateway rodando:

curl http://localhost:8644/health

Resposta esperada:

{"status": "ok", "platform": "webhook"}

Configurando rotas​

As rotas definem como cada origem de webhook é tratada. Cada rota é uma entrada nomeada sob platforms.webhook.extra.routes no seu config.yaml.

Propriedades da rota​

PropriedadeObrigatóriaDescrição
eventsNãoLista de tipos de evento a aceitar (por exemplo, ["pull_request"]). Se vazia, todos os eventos são aceitos. O tipo do evento é lido de X-GitHub-Event, X-GitLab-Event ou do event_type no payload.
secretSimSegredo HMAC para validar a assinatura. Se não estiver definido na rota, cai para o secret global. Use "INSECURE_NO_AUTH" só em testes (pula a validação).
profileNãoPerfil autorizado a executar esta rota quando gateway.multiplex_profiles está ativado. Omita para uma rota restrita ao perfil padrão; informe um nome de perfil (por exemplo, coder) para vincular a rota e o segredo dela a /p/coder/webhooks/<rota>.
promptNãoTemplate com acesso ao payload em notação de ponto (por exemplo, {pull_request.title}). Se omitido, o payload JSON inteiro é despejado no prompt. Os campos do payload não são confiáveis — veja Autenticado não quer dizer confiável.
filtersNãoFiltros declarativos sobre o payload, avaliados depois da filtragem de autenticação/corpo/evento e antes do trabalho do agente ou da entrega direta. Quem não casa recebe {"status":"ignored","reason":"filter"} com HTTP 200.
scriptNãoScript de filtro/transformação dentro de ~/.hermes/scripts/. O payload do webhook chega como JSON na stdin. Uma saída JSON de objeto na stdout substitui o payload antes do template; saída de texto vira script_output; stdout vazia, [SILENT] ou código de saída diferente de zero fazem o webhook ser ignorado.
skillsNãoLista de nomes de skills a carregar na execução do agente.
toolsetsNãoLista de chaves de toolset (por exemplo, ["terminal", "file", "web"]) que substitui o toolset de webhook definido no nível da plataforma, apenas para as execuções disparadas por esta rota. Só por edição manual do config — não dá para definir via hermes webhook subscribe, então assinaturas criadas pelo agente não podem se autoconceder ferramentas elevadas. Os nomes são validados do mesmo jeito que as entradas de platform_toolsets (nomes desconhecidos ou restritos por plataforma são descartados). Veja Toolsets por rota.
deliverNãoPara onde mandar a resposta: github_comment, telegram, discord, slack, signal, sms, whatsapp, matrix, mattermost, homeassistant, email, dingtalk, feishu, wecom, weixin, bluebubbles, qqbot ou log (padrão).
deliver_extraNãoConfiguração extra de entrega — as chaves dependem do tipo de deliver (por exemplo, repo, pr_number, chat_id). Os valores aceitam os mesmos templates {notacao.de.ponto} do prompt.
deliver_onlyNãoSe true, pula o agente por completo — o template prompt renderizado vira literalmente a mensagem entregue. Custo zero de LLM, entrega em menos de um segundo. Veja Modo de entrega direta para casos de uso. Exige que deliver seja um destino real (não log).

Exemplo completo​

platforms:
webhook:
enabled: true
extra:
port: 8644
secret: "segredo-global-de-reserva"
routes:
github-pr:
events: ["pull_request"]
secret: "github-webhook-secret"
prompt: |
Revise este pull request:
Repositório: {repository.full_name}
PR #{number}: {pull_request.title}
Autor: {pull_request.user.login}
URL: {pull_request.html_url}
URL do diff: {pull_request.diff_url}
Ação: {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: "Novo push em {repository.full_name} na branch {ref}: {head_commit.message}"
filters:
- field: "ref"
equals: "refs/heads/main"
deliver: "telegram"

Filtros de payload​

Use filters quando o provedor manda um fluxo largo de eventos mas só alguns payloads devem acordar o agente ou disparar a entrega com deliver_only. Os filtros rodam depois da validação de assinatura, do parsing do corpo e do events, mas antes da renderização do prompt, da idempotência, do despacho para o agente ou da entrega direta.

platforms:
webhook:
extra:
routes:
todoist:
events: ["item:updated"]
secret: "todoist-secret"
filters:
- field: "payload.labels"
contains: "hermes"
- any:
- field: "payload.priority"
equals: 4
- field: "payload.project_id"
in_file: "~/.hermes/data/todoist/watchlist.json"
prompt: "Tarefa do Todoist mudou: {payload.content}"

Operadores suportados:

  • exists: true|false
  • missing: true
  • equals / not_equals
  • contains para strings, listas e chaves de dicionário
  • in para listas escritas na hora
  • in_file para arrays JSON, objetos JSON (as chaves são usadas) ou arquivos de texto com um item por linha
  • regex
  • grupos all, any e not

Os caminhos de campo usam notação de ponto. payload.foo lê de um objeto payload de primeiro nível quando ele existe, ou da raiz do corpo do webhook em payloads planos. event / event_type casam com o tipo de evento resolvido, e headers.<Nome> lê cabeçalhos da requisição.

Filtros e transformações por script​

Use script quando os filtros declarativos não bastarem. Os scripts precisam ficar dentro de ~/.hermes/scripts/ do perfil ativo; caminhos relativos resolvem lá dentro, e sair desse diretório por path traversal é bloqueado. Scripts .sh e .bash rodam com bash, e todas as outras extensões rodam com o interpretador Python atual.

O payload da rota é enviado para a stdin como JSON:

# ~/.hermes/scripts/todoist-hermes-label.py
import json
import sys

payload = json.load(sys.stdin)
labels = payload.get("payload", {}).get("labels", [])
if "hermes" not in labels:
print("[SILENT]")
raise SystemExit(0)

payload["body"] = payload["payload"]["content"]
print(json.dumps(payload))

Resultados possíveis do script:

  • Saída JSON de objeto na stdout substitui o payload usado por prompt e deliver_extra.
  • Saída de texto que não é JSON é adicionada ao payload como script_output.
  • Stdout vazia, exatamente [SILENT], {"__hermes_ignore__": true}, timeout, script inexistente ou código de saída diferente de zero devolvem HTTP 200 com {"status":"ignored","reason":"script"}.

Templates de prompt​

Os prompts usam notação de ponto para acessar campos aninhados do payload do webhook:

  • {pull_request.title} resolve para payload["pull_request"]["title"]
  • {repository.full_name} resolve para payload["repository"]["full_name"]
  • {__raw__} — token especial que despeja o payload inteiro como JSON indentado (truncado em 4000 caracteres). Útil para alertas de monitoramento ou webhooks genéricos, quando o agente precisa do contexto completo.
  • Chaves que não existem ficam como a string literal {chave} (sem erro)
  • Dicionários e listas aninhados são serializados em JSON e truncados em 2000 caracteres

Você pode misturar {__raw__} com variáveis normais de template:

prompt: "PR #{pull_request.number} por {pull_request.user.login}: {__raw__}"

Se nenhum template prompt estiver configurado para a rota, o payload inteiro é despejado como JSON indentado (truncado em 4000 caracteres).

Os mesmos templates em notação de ponto funcionam nos valores de deliver_extra.

Entrega em tópico de fórum​

Ao entregar respostas de webhook no Telegram, você pode mirar um tópico de fórum específico incluindo message_thread_id (ou thread_id) no deliver_extra:

webhooks:
routes:
alerts:
events: ["alert"]
prompt: "Alerta: {__raw__}"
deliver: "telegram"
deliver_extra:
chat_id: "-1001234567890"
message_thread_id: "42"

Se chat_id não for informado no deliver_extra, a entrega cai para o canal padrão configurado na plataforma de destino.


Revisão de PR no GitHub (passo a passo)​

Este passo a passo configura revisão de código automática em todo pull request.

1. Crie o webhook no GitHub​

  1. Vá no seu repositório → Settings → Webhooks → Add webhook
  2. Defina Payload URL como http://seu-servidor:8644/webhooks/github-pr
  3. Defina Content type como application/json
  4. Defina Secret igual ao da config da sua rota (por exemplo, github-webhook-secret)
  5. Em Which events?, escolha Let me select individual events e marque Pull requests
  6. Clique em Add webhook

2. Adicione a config da rota​

Adicione a rota github-pr ao seu ~/.hermes/config.yaml, como no exemplo acima.

3. Garanta que a CLI gh está autenticada​

O tipo de entrega github_comment usa a CLI do GitHub para publicar comentários:

gh auth login

4. Teste​

Abra um pull request no repositório. O webhook dispara, o Hermes processa o evento e publica um comentário de revisão no PR.


Configuração de webhook no GitLab​

Os webhooks do GitLab funcionam de forma parecida, mas usam outro mecanismo de autenticação. O GitLab manda o segredo como um cabeçalho X-Gitlab-Token em texto puro (comparação exata de string, não HMAC).

1. Crie o webhook no GitLab​

  1. Vá no seu projeto → Settings → Webhooks
  2. Defina a URL como http://seu-servidor:8644/webhooks/gitlab-mr
  3. Informe seu Secret token
  4. Selecione Merge request events (e quaisquer outros eventos que você queira)
  5. Clique em Add webhook

2. Adicione a config da rota​

platforms:
webhook:
enabled: true
extra:
routes:
gitlab-mr:
events: ["merge_request"]
secret: "seu-token-secreto-do-gitlab"
prompt: |
Revise este merge request:
Projeto: {project.path_with_namespace}
MR !{object_attributes.iid}: {object_attributes.title}
Autor: {object_attributes.last_commit.author.name}
URL: {object_attributes.url}
Ação: {object_attributes.action}
deliver: "log"

Opções de entrega​

O campo deliver controla para onde vai a resposta do agente depois que o evento do webhook é processado.

Tipo de entregaDescrição
logRegistra a resposta na saída de log do gateway. É o padrão e serve bem para testes.
github_commentPublica a resposta como comentário em PR/issue pela CLI gh. Exige deliver_extra.repo e deliver_extra.pr_number. A CLI gh precisa estar instalada e autenticada na máquina do gateway (gh auth login).
telegramEncaminha a resposta para o Telegram. Usa o canal padrão, ou informe chat_id em deliver_extra.
discordEncaminha a resposta para o Discord. Usa o canal padrão, ou informe chat_id em deliver_extra.
slackEncaminha a resposta para o Slack. Usa o canal padrão, ou informe chat_id em deliver_extra.
signalEncaminha a resposta para o Signal. Usa o canal padrão, ou informe chat_id em deliver_extra.
smsEncaminha a resposta por SMS via Twilio. Usa o canal padrão, ou informe chat_id em deliver_extra.
whatsappEncaminha a resposta para o WhatsApp. Usa o canal padrão, ou informe chat_id em deliver_extra.
matrixEncaminha a resposta para o Matrix. Usa o canal padrão, ou informe chat_id em deliver_extra.
mattermostEncaminha a resposta para o Mattermost. Usa o canal padrão, ou informe chat_id em deliver_extra.
homeassistantEncaminha a resposta para o Home Assistant. Usa o canal padrão, ou informe chat_id em deliver_extra.
emailEncaminha a resposta por e-mail. Usa o canal padrão, ou informe chat_id em deliver_extra.
dingtalkEncaminha a resposta para o DingTalk. Usa o canal padrão, ou informe chat_id em deliver_extra.
feishuEncaminha a resposta para o Feishu/Lark. Usa o canal padrão, ou informe chat_id em deliver_extra.
wecomEncaminha a resposta para o WeCom. Usa o canal padrão, ou informe chat_id em deliver_extra.
weixinEncaminha a resposta para o Weixin (WeChat). Usa o canal padrão, ou informe chat_id em deliver_extra.
bluebubblesEncaminha a resposta para o BlueBubbles (iMessage). Usa o canal padrão, ou informe chat_id em deliver_extra.

Para entrega entre plataformas, a plataforma de destino também precisa estar ativada e conectada no gateway. Se nenhum chat_id for informado em deliver_extra, a resposta vai para o canal padrão configurado naquela plataforma.


Modo de entrega direta​

Por padrão, todo POST de webhook dispara uma execução do agente — o payload vira prompt, o agente processa e a resposta dele é entregue. Isso gasta tokens de LLM em cada evento.

Para casos em que você só quer empurrar uma notificação simples — sem raciocínio, sem laço do agente, só entregar a mensagem — defina deliver_only: true na rota. O template prompt renderizado vira literalmente o corpo da mensagem, e o adaptador despacha direto para o destino de entrega configurado.

Quando usar a entrega direta​

  • Push de serviço externo — webhook do Supabase/Firebase dispara em uma mudança no banco → avisa um usuário no Telegram na hora
  • Alertas de monitoramento — webhook de alerta do Datadog/Grafana → empurra para um canal do Discord
  • Pings entre agentes — o Agente A avisa o usuário do Agente B que uma tarefa longa terminou
  • Conclusão de job em segundo plano — o cron termina → publica o resultado no Slack

Vantagens:

  • Zero tokens de LLM — o agente nunca é chamado
  • Entrega em menos de um segundo — uma única chamada de adaptador, sem laço de raciocínio
  • Mesma segurança do modo com agente — autenticação HMAC, limites de taxa, idempotência e limites de tamanho de corpo continuam valendo
  • Resposta síncrona — o POST devolve 200 OK assim que a entrega dá certo, ou 502 se o destino recusa, então seu serviço de origem pode reagir com uma nova tentativa inteligente

Exemplo: push no Telegram a partir do Supabase​

platforms:
webhook:
enabled: true
extra:
port: 8644
secret: "segredo-global"
routes:
antenna-matches:
secret: "antenna-webhook-secret"
deliver: "telegram"
deliver_only: true
prompt: "🎉 Novo match: {match.user_name} deu match com você!"
deliver_extra:
chat_id: "{match.telegram_chat_id}"

Sua edge function do Supabase assina o payload com HMAC-SHA256 e faz POST em https://seu-servidor:8644/webhooks/antenna-matches. O adaptador de webhook valida a assinatura, renderiza o template a partir do payload, entrega no Telegram e devolve 200 OK.

Exemplo: assinatura dinâmica pela CLI​

hermes webhook subscribe antenna-matches \
--deliver telegram \
--deliver-chat-id "123456789" \
--deliver-only \
--prompt "🎉 Novo match: {match.user_name} deu match com você!" \
--description "Notificações de match do Antenna"

Códigos de resposta​

StatusSignificado
200 OKEntregue com sucesso. Corpo: {"status": "delivered", "route": "...", "target": "...", "delivery_id": "..."}
200 OK (status=duplicate)ID X-GitHub-Delivery duplicado dentro do TTL de idempotência (1 hora). Não é reentregue.
401 UnauthorizedAssinatura HMAC inválida ou ausente.
400 Bad RequestCorpo JSON malformado.
404 Not FoundNome de rota desconhecido.
413 Payload Too LargeO corpo passou de max_body_bytes.
429 Too Many RequestsLimite de taxa da rota estourado.
502 Bad GatewayO adaptador de destino recusou a mensagem ou levantou erro. O erro fica no log do servidor; o corpo da resposta é um Delivery failed genérico, para não vazar detalhes internos do adaptador.

Pegadinhas de configuração​

  • deliver_only: true exige que deliver seja um destino real. deliver: log (ou omitir deliver) é rejeitado na inicialização — o adaptador se recusa a subir se encontrar uma rota mal configurada.
  • O campo skills é ignorado no modo de entrega direta (nenhum agente roda, então não há onde injetar skills).
  • A renderização do template usa a mesma sintaxe {notacao.de.ponto} do modo com agente, incluindo o token {__raw__}.
  • A idempotência usa o mesmo cabeçalho X-GitHub-Delivery / X-Request-ID — novas tentativas com o mesmo ID devolvem status=duplicate e NÃO reentregam.

Assinaturas dinâmicas (CLI)​

Além das rotas estáticas no config.yaml, você pode criar assinaturas de webhook dinamicamente com o comando hermes webhook da CLI. Isso é especialmente útil quando é o próprio agente que precisa montar gatilhos orientados a evento.

Crie uma assinatura​

hermes webhook subscribe github-issues \
--events "issues" \
--prompt "Nova issue #{issue.number}: {issue.title}\nPor: {issue.user.login}\n\n{issue.body}" \
--deliver telegram \
--deliver-chat-id "-100123456789" \
--description "Triagem de novas issues do GitHub"

Isso devolve a URL do webhook e um segredo HMAC gerado automaticamente. Configure seu serviço para fazer POST nessa URL.

Liste as assinaturas​

hermes webhook list

Remova uma assinatura​

hermes webhook remove github-issues

Teste uma assinatura​

hermes webhook test github-issues
hermes webhook test github-issues --payload '{"issue": {"number": 42, "title": "Test"}}'

Como funcionam as assinaturas dinâmicas​

  • As assinaturas ficam guardadas em ~/.hermes/webhook_subscriptions.json
  • O adaptador de webhook recarrega esse arquivo a quente a cada requisição recebida (com checagem de mtime, custo desprezível)
  • Rotas estáticas do config.yaml sempre têm precedência sobre as dinâmicas de mesmo nome
  • As assinaturas dinâmicas usam o mesmo formato e as mesmas capacidades das rotas estáticas (eventos, templates de prompt, skills, entrega)
  • Não precisa reiniciar o gateway — assinou, já está no ar

Assinaturas criadas pelo agente​

O agente pode criar assinaturas pela ferramenta de terminal quando guiado pela skill webhook-subscriptions. Peça ao agente para "configurar um webhook para issues do GitHub" e ele vai rodar o comando hermes webhook subscribe apropriado.


Toolsets por rota​

As execuções de agente por webhook usam por padrão um toolset propositalmente restrito (web_search, web_extract, vision_analyze, clarify), porque payloads de webhook podem carregar conteúdo de terceiros não confiável — o título de um PR público ou o comentário de uma issue nunca deveriam conseguir abrir caminho até o seu terminal por prompt injection.

Para rotas confiáveis — um daemon de monitoramento no localhost mandando alertas de sistema, um sistema de CI interno — você pode conceder um toolset mais amplo só para aquela rota, sem alargar todas as outras rotas de webhook:

platforms:
webhook:
enabled: true
extra:
routes:
oom-emergency:
secret: "monitor-secret"
prompt: "Emergência de memória: {detail}. Diagnostique com ps/free/py-spy e relate."
toolsets: ["terminal", "file", "code_execution", "web"]
deliver: "telegram"

Para assinaturas dinâmicas, adicione a chave toolsets editando o ~/.hermes/webhook_subscriptions.json direto:

{
"oom-emergency": {
"secret": "...",
"prompt": "...",
"toolsets": ["terminal", "file", "web"],
"deliver": "telegram"
}
}

Comportamento e propriedades de segurança:

  • A lista da rota substitui a resolução de toolset de webhook do nível da plataforma para as execuções daquela rota (não há mesclagem).
  • Os nomes passam pela mesma validação da config platform_toolsets — nomes desconhecidos e toolsets restritos por plataforma são descartados.
  • O hermes webhook subscribe propositalmente não aceita uma flag de toolsets. Conceder ferramentas elevadas é uma edição manual no arquivo de config, então um agente que cria a própria assinatura em tempo de execução não consegue se autoconceder terminal.
  • Só conceda toolsets elevados a rotas cujos remetentes você controla por completo, e com um segredo HMAC de verdade. Quem conseguir fazer POST de um payload assinado validamente nessa rota está, na prática, rodando um agente com essas ferramentas.

Segurança​

O adaptador de webhook tem várias camadas de segurança:

Validação de assinatura HMAC​

O adaptador valida as assinaturas dos webhooks recebidos usando o método apropriado para cada origem:

  • GitHub: cabeçalho X-Hub-Signature-256 — digest hex HMAC-SHA256 com prefixo sha256=
  • GitLab: cabeçalho X-Gitlab-Token — comparação simples da string do segredo
  • Genérico (V2, recomendado): cabeçalhos X-Webhook-Signature-V2 + X-Webhook-Timestamp — digest hex HMAC-SHA256 de <timestamp>.<corpo>. O timestamp (segundos Unix) precisa estar a no máximo ±300 segundos do relógio do servidor, o que impede que requisições capturadas sejam repetidas depois.
  • Genérico (V1, legado): cabeçalho X-Webhook-Signature — digest hex HMAC-SHA256 cru só do corpo. Ainda é aceito por compatibilidade, mas não tem proteção contra replay (uma requisição capturada pode ser repetida indefinidamente); o gateway registra um aviso de descontinuação uma vez por rota. Migre os remetentes para o V2.

Se houver um segredo configurado mas nenhum cabeçalho de assinatura reconhecido chegar, a requisição é rejeitada.

O segredo é obrigatório​

Toda rota precisa de um segredo — definido diretamente nela ou herdado do secret global. Rotas sem segredo fazem o adaptador falhar na inicialização com um erro. Só para desenvolvimento/teste, você pode definir o segredo como "INSECURE_NO_AUTH" para pular a validação por completo.

Quando o roteamento multiperfil está ativado, o campo profile da rota também vincula esse segredo a um alvo de execução. Uma rota sem profile só vale para o perfil padrão. Uma requisição com assinatura de rota válida ainda é rejeitada se o prefixo /p/<profile>/ dela não bater com o vínculo da rota.

O INSECURE_NO_AUTH só é aceito quando o gateway está ligado a um host de loopback (127.0.0.1, localhost, ::1). Se ele for combinado com um bind que não é loopback, como 0.0.0.0 ou um IP da LAN, o adaptador se recusa a subir — isso evita expor sem querer um endpoint sem autenticação em uma interface pública.

Limite de taxa​

Cada rota tem limite de 30 requisições por minuto por padrão (janela fixa). Configure isso globalmente:

platforms:
webhook:
extra:
rate_limit: 60 # requisições por minuto

Requisições acima do limite recebem uma resposta 429 Too Many Requests.

Idempotência​

Os IDs de entrega (de X-GitHub-Delivery, X-Request-ID ou de um timestamp como reserva) ficam em cache por 1 hora. Entregas duplicadas (por exemplo, novas tentativas do webhook) são ignoradas em silêncio com uma resposta 200, evitando execuções repetidas do agente.

Limites de tamanho do corpo​

Payloads acima de 1 MB são rejeitados antes de o corpo ser lido. Configure assim:

platforms:
webhook:
extra:
max_body_bytes: 2097152 # 2 MB

Autenticado não quer dizer confiável​

aviso

A validação HMAC autentica o remetente, não o conteúdo. Uma assinatura válida só prova que a requisição veio de alguém que tem o segredo da rota (o GitHub, por exemplo). Ela não diz nada sobre quem escreveu os campos de negócio dentro do payload — títulos de PR, mensagens de commit, descrições de issue e qualquer outro texto vindo de fora são escritos por terceiros quaisquer e precisam ser tratados como não confiáveis.

É o mesmo modelo de confiança que vale para tudo que o agente lê: páginas web, arquivos e saídas de ferramentas são todos entrada não confiável. O Hermes não sanitiza — e nem tem como sanitizar de forma confiável — texto não confiável com uma lista de bloqueio; formulação, codificação e tradução tornam isso trivialmente contornável. A fronteira de confiança é a superfície de capacidades do agente, não o canal de entrada. É lá que você endurece:

  • Coloque o runtime em sandbox. Rode o gateway com o backend de terminal Docker ou SSH (ou dentro de uma VM) quando ele estiver exposto à internet, para que um turno sequestrado não alcance a máquina hospedeira.
  • Restrinja o toolset. Desative terminal, file e ferramentas de ação externa nas sessões disparadas por webhook, se a rota só precisa ler e resumir. Menos capacidades significa um raio de destruição menor caso algum campo do payload traga instruções injetadas.
  • Mantenha as aprovações ligadas para qualquer operação destrutiva ou de saída, para que uma instrução injetada não possa agir sozinha.
  • Faça templates estreitos. Prefira um prompt específico com campos nomeados ({pull_request.title}) em vez de {__raw__} ou de um template vazio que despeja o payload inteiro, para que só os campos que você quer cheguem ao prompt.

Resolução de problemas​

O webhook não chega​

  • Confira se a porta está exposta e acessível a partir da origem do webhook
  • Revise as regras de firewall — a porta 8644 (ou a que você configurou) precisa estar aberta
  • Confira se o caminho da URL bate: http://seu-servidor:8644/webhooks/<nome-da-rota>
  • Use o endpoint /health para confirmar que o servidor está no ar

A validação de assinatura falha​

  • Garanta que o segredo na config da rota é exatamente igual ao segredo configurado na origem do webhook
  • No GitHub, o segredo é baseado em HMAC — confira o X-Hub-Signature-256
  • No GitLab, o segredo é comparação simples de token — confira o X-Gitlab-Token
  • Procure avisos de Invalid signature nos logs do gateway

O evento está sendo ignorado​

  • Verifique se o tipo do evento está na lista events da sua rota
  • Os eventos do GitHub usam valores como pull_request, push, issues (o valor do cabeçalho X-GitHub-Event)
  • Os eventos do GitLab usam valores como merge_request, push (o valor do cabeçalho X-GitLab-Event)
  • Se events estiver vazio ou não definido, todos os eventos são aceitos

O agente não responde​

  • Rode o gateway em primeiro plano para ver os logs: hermes gateway run
  • Confira se o template do prompt está sendo renderizado direito
  • Verifique se o destino de entrega está configurado e conectado

Respostas duplicadas​

  • O cache de idempotência deveria evitar isso — confira se a origem do webhook está mandando um cabeçalho de ID de entrega (X-GitHub-Delivery ou X-Request-ID)
  • Os IDs de entrega ficam em cache por 1 hora

Erros da CLI gh (entrega por comentário no GitHub)​

  • Rode gh auth login na máquina do gateway
  • Garanta que o usuário do GitHub autenticado tem acesso de escrita ao repositório
  • Confira se o gh está instalado e no PATH

Variáveis de ambiente​

VariávelDescriçãoPadrão
WEBHOOK_ENABLEDAtiva o adaptador de plataforma de webhookfalse
WEBHOOK_PORTPorta do servidor HTTP que recebe os webhooks8644
WEBHOOK_SECRETSegredo HMAC global (usado como reserva quando as rotas não definem o próprio)(nenhum)