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
- Ative pelo
hermes gateway setupou por variáveis de ambiente - Defina rotas no
config.yamlou crie rotas dinamicamente comhermes webhook subscribe - 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
| Propriedade | Obrigatória | Descrição |
|---|---|---|
events | Não | Lista 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. |
secret | Sim | Segredo 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). |
profile | Não | Perfil 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>. |
prompt | Não | Template 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. |
filters | Não | Filtros 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. |
script | Não | Script 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. |
skills | Não | Lista de nomes de skills a carregar na execução do agente. |
toolsets | Não | Lista 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. |
deliver | Não | Para 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_extra | Não | Configuraçã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_only | Não | Se 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|falsemissing: trueequals/not_equalscontainspara strings, listas e chaves de dicionárioinpara listas escritas na horain_filepara arrays JSON, objetos JSON (as chaves são usadas) ou arquivos de texto com um item por linharegex- grupos
all,anyenot
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
promptedeliver_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 parapayload["pull_request"]["title"]{repository.full_name}resolve parapayload["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
- Vá no seu repositório → Settings → Webhooks → Add webhook
- Defina Payload URL como
http://seu-servidor:8644/webhooks/github-pr - Defina Content type como
application/json - Defina Secret igual ao da config da sua rota (por exemplo,
github-webhook-secret) - Em Which events?, escolha Let me select individual events e marque Pull requests
- 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
- Vá no seu projeto → Settings → Webhooks
- Defina a URL como
http://seu-servidor:8644/webhooks/gitlab-mr - Informe seu Secret token
- Selecione Merge request events (e quaisquer outros eventos que você queira)
- 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 entrega | Descrição |
|---|---|
log | Registra a resposta na saída de log do gateway. É o padrão e serve bem para testes. |
github_comment | Publica 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). |
telegram | Encaminha a resposta para o Telegram. Usa o canal padrão, ou informe chat_id em deliver_extra. |
discord | Encaminha a resposta para o Discord. Usa o canal padrão, ou informe chat_id em deliver_extra. |
slack | Encaminha a resposta para o Slack. Usa o canal padrão, ou informe chat_id em deliver_extra. |
signal | Encaminha a resposta para o Signal. Usa o canal padrão, ou informe chat_id em deliver_extra. |
sms | Encaminha a resposta por SMS via Twilio. Usa o canal padrão, ou informe chat_id em deliver_extra. |
whatsapp | Encaminha a resposta para o WhatsApp. Usa o canal padrão, ou informe chat_id em deliver_extra. |
matrix | Encaminha a resposta para o Matrix. Usa o canal padrão, ou informe chat_id em deliver_extra. |
mattermost | Encaminha a resposta para o Mattermost. Usa o canal padrão, ou informe chat_id em deliver_extra. |
homeassistant | Encaminha a resposta para o Home Assistant. Usa o canal padrão, ou informe chat_id em deliver_extra. |
email | Encaminha a resposta por e-mail. Usa o canal padrão, ou informe chat_id em deliver_extra. |
dingtalk | Encaminha a resposta para o DingTalk. Usa o canal padrão, ou informe chat_id em deliver_extra. |
feishu | Encaminha a resposta para o Feishu/Lark. Usa o canal padrão, ou informe chat_id em deliver_extra. |
wecom | Encaminha a resposta para o WeCom. Usa o canal padrão, ou informe chat_id em deliver_extra. |
weixin | Encaminha a resposta para o Weixin (WeChat). Usa o canal padrão, ou informe chat_id em deliver_extra. |
bluebubbles | Encaminha 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 OKassim que a entrega dá certo, ou502se 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
| Status | Significado |
|---|---|
200 OK | Entregue 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 Unauthorized | Assinatura HMAC inválida ou ausente. |
400 Bad Request | Corpo JSON malformado. |
404 Not Found | Nome de rota desconhecido. |
413 Payload Too Large | O corpo passou de max_body_bytes. |
429 Too Many Requests | Limite de taxa da rota estourado. |
502 Bad Gateway | O 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: trueexige quedeliverseja um destino real.deliver: log(ou omitirdeliver) é 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 devolvemstatus=duplicatee 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.yamlsempre 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 subscribepropositalmente 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 autoconcederterminal. - 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 prefixosha256= - 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
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,filee 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
promptespecí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
/healthpara 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 signaturenos logs do gateway
O evento está sendo ignorado
- Verifique se o tipo do evento está na lista
eventsda sua rota - Os eventos do GitHub usam valores como
pull_request,push,issues(o valor do cabeçalhoX-GitHub-Event) - Os eventos do GitLab usam valores como
merge_request,push(o valor do cabeçalhoX-GitLab-Event) - Se
eventsestiver 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-DeliveryouX-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 loginna máquina do gateway - Garanta que o usuário do GitHub autenticado tem acesso de escrita ao repositório
- Confira se o
ghestá instalado e no PATH
Variáveis de ambiente
| Variável | Descrição | Padrão |
|---|---|---|
WEBHOOK_ENABLED | Ativa o adaptador de plataforma de webhook | false |
WEBHOOK_PORT | Porta do servidor HTTP que recebe os webhooks | 8644 |
WEBHOOK_SECRET | Segredo HMAC global (usado como reserva quando as rotas não definem o próprio) | (nenhum) |