Notificações — Telegram e webhook
O Runner envia notificações para cada evento relevante de deploy. Esta página cobre como configurar Telegram e webhook, quais eventos são disparados e como sobrescrever a config global por app.
Pré-requisitos
- Runner v2.30.10+
- Acesso a
/opt/runner/config.ymlno servidor - Para Telegram: conta Telegram e acesso ao @BotFather
- Para webhook: URL HTTPS acessível pelo servidor Runner
Onde fica a configuração
A config de notificações fica na seção notify: do /opt/runner/config.yml. Esta é a config global — vale para todas as apps do servidor.
# /opt/runner/config.yml
notify:
channel: telegram # telegram | webhook | both
telegram_bot_token: "123456:ABC-DEF..."
telegram_chat_id: "-100123456789"
webhook_url: "https://hooks.exemplo.com/runner"
# Classe deploy (iniciado/concluído/falha) — gate 2. default true.
# false = nenhuma notificação de chat do ciclo de deploy (audit log continua).
enabled: true
# Extras (gate 3) — todos opt-in, default false:
extras:
batch_summary: false # resumo "Runner deploy batch — N ok, M failed"
verbose_errors: false # 2ª msg com o erro completo numa falha
health_status: false # status do health check pós-deploy
init_log: false # log do boot do container (⚠️ pode vazar segredos)Migração (v2.42.x → modelo de classes): as chaves antigas top-level
verbose_errors/notify_successforam substituídas. Configs antigos não quebram (chaves desconhecidas são ignoradas), mas não migram sozinhos —enabledcai no defaulttruee osextrasemfalse. Reescreva no formato novo para reativar verbose errors etc.
Modelo de classes e os 3 gates
As notificações são organizadas em classes de evento, e cada classe passa por até 3 gates antes de chegar ao chat. Os gates valem para todos os canais de chat configurados (Telegram, Discord, webhook) de forma uniforme — não há tratativa por canal.
| Gate | O que é | Controla |
|---|---|---|
| 1 | Canal configurado | Existe telegram_* / discord_webhook / webhook_url? |
| 2 | enabled |
Liga/desliga a classe deploy (iniciado/concluído/falha) |
| 3 | extras.<x> |
Liga cada evento extra (opt-in) |
Policy por classe:
| Classe | Vai pro chat quando |
|---|---|
| Deploy (iniciado/concluído/falha) | enabled |
| Batch summary | extras.batch_summary (independe do enabled) |
| Verbose error (2ª msg) | enabled e extras.verbose_errors |
| Health status | enabled e extras.health_status |
| Init log | enabled e extras.init_log |
Journal sempre-on: o audit log
deploy-events.jsonlregistra todos os eventos sempre, independentemente dos gates. Os gates só decidem o que vai pro chat — o histórico (runner audit/runner logs) nunca perde evento.
Exemplos:
- Padrão (
enabled: true, extrasfalse): chat recebe iniciado/concluído/falha; sem batch, sem 2ª msg de erro, sem health/init log. - Silenciar deploy no chat (
enabled: false): nenhum evento de deploy no chat (nem falha) — só o journal registra. Útil quando o batch summary já basta. - Só o resumo de batch:
enabled: false+extras.batch_summary: true.
Setup Telegram
1. Criar o bot
- Abra o Telegram e busque
@BotFather - Envie
/newbote siga as instruções - Copie o token (formato:
123456789:ABCdefGHI...)
2. Obter o chat_id
Para DM ou grupo — use o setup interativo:
runner notify setup --channel telegramO comando valida o token via API do Telegram, detecta o chat_id automaticamente e salva no config.yml.
Para canal — o bot não recebe getUpdates de canais. Obtenha o chat_id manualmente:
curl -s "https://api.telegram.org/bot<TOKEN>/getUpdates"Envie uma mensagem no grupo ou canal primeiro, depois rode o comando. O chat_id fica em .result[].message.chat.id — negativo para grupos e canais.
3. Configurar manualmente
notify:
channel: telegram
telegram_bot_token: "123456789:ABCdefGHI..."
telegram_chat_id: "-100123456789"Para canal: adicione o bot como administrador com permissão "Post Messages" antes de usar.
4. Testar
runner notify testO comando envia uma mensagem de teste ao canal configurado. Se falhar, rode runner notify status para ver o diagnóstico.
Setup webhook
O Runner faz POST JSON para a URL configurada a cada evento.
O webhook é um canal de notificação como Telegram/Discord — passa pelos
mesmos gates (enabled / extras.*) de forma uniforme. Ou seja, com
enabled: false o webhook não recebe os eventos do ciclo de deploy
(iniciado/concluído/falha); os extras (batch_summary, etc.) só chegam se o
extras.<x> correspondente estiver ligado. Para consumir todos os eventos
programaticamente sem depender dos gates, leia o audit log deploy-events.jsonl
(sempre-on) em vez do webhook.
Configurar
notify:
channel: webhook
webhook_url: "https://hooks.exemplo.com/runner"Payload recebido
{
"event_type": "deploy_success",
"project": "meu-app",
"instance": "production",
"version": "v1.2.0",
"previous_version": "v1.1.9",
"deploy_id": "a3f7c2d1",
"timestamp": "2026-05-27T14:30:00Z",
"branch": "main",
"pr_number": 41,
"expires_at": "2026-05-29T14:30:00Z",
"last_commit": {
"author": "alice",
"hash": "ee8a4bf",
"subject": "CONFORMITY front: tokenized footer (#41)"
},
"commit_authors": ["alice", "bob"],
"commits_count": 3,
"commits": [
{ "author": "alice", "hash": "ee8a4bf", "subject": "fix(api): import nextTick" },
{ "author": "bob", "hash": "9afcdbe", "subject": "fix(api): restore KPIs" }
]
}Campos opcionais (v2.30.12+):
branch— branch que originou o deploypr_number— PR detectado (squash trailer(#N), merge commit, ourunner add --pr N)expires_at— TTL do PR preview (ISO 8601), quando aplicávellast_commit— autor, hash e subject do commit real mais novo do deploycommit_authors— autores únicos do range (multi-commit)commits_count— total de commits reais reconhecidos no deploycommits— lista dos commits reais do range (newest-first, capada), com os auto-commitschore: update encrypted secretsfiltrados
Para eventos de falha, o payload inclui também "error": "<mensagem>".
Usar Telegram e webhook simultaneamente
notify:
channel: both
telegram_bot_token: "123456789:ABCdefGHI..."
telegram_chat_id: "-100123456789"
webhook_url: "https://hooks.exemplo.com/runner"Eventos disparados
event_type |
Quando | O que inclui |
|---|---|---|
deploy_started |
Início do deploy (antes do fetch) | projeto, instância, versão, branch |
deploy_success |
Deploy termina com healthcheck OK | projeto, instância, versão, duração — chat conforme enabled (audit log sempre) |
deploy_failure |
Deploy falha ou healthcheck não passa | projeto, instância, versão, erro extraído |
rollback_started |
Rollback automático iniciado | projeto, instância, versão alvo |
rollback_success |
Rollback automático concluído | projeto, instância, versão revertida |
health_check_failed |
Healthcheck falhou após N retries | projeto, instância, versão, modo |
health_skipped |
healthcheck.mode: skip_check ativo (warn) |
projeto, instância |
canary_weight_changed |
Peso ajustado no scheduler | projeto, instância, peso anterior, peso novo |
canary_promoted |
Canary chegou a 100% | projeto, instância, versão promovida |
instance_destroyed |
runner destroy <instance> |
projeto, instância |
pr_expired |
runner cleanup --expired destruiu PR vencido (v2.30.12+) |
projeto, instância, pr_number |
Commits reais na notificação de deploy
A lista de commits aparece na mensagem de início (🚀 Deploy iniciado) — pra
você ver o que está sendo deployado e diagnosticar um possível erro antes de a
conclusão chegar. Quando o deploy reconhece vários commits, lista os commits
reais do range (📝 Commits:, até 5, com trailer … +N mais). Os auto-commits
internos chore: update encrypted secrets (que o Runner cria ao persistir
.runner/secrets.enc) são filtrados — você vê o que de fato foi deployado, não o
auto-commit. Os mesmos campos vão pro payload do webhook (commits,
commits_count, commit_authors) e pro audit log, em todos os eventos.
Template Telegram/Discord (v2.43.2+)
O header lidera com a ação; a identidade é textual (sem badge de cor de
ambiente). Projeto: só aparece quando project_group está setado no .deploy.yml.
Início (traz domínio, branch e os commits):
🚀 Deploy iniciado
Deploy: sweepapex-useradmin-front
Projeto: Sweepapex
🌐 Instance: production
🔗 admin.luckypeaksweeps.com
🏷️ fc4086f ← ca73d54
🌿 Branch: feature/prod-deploy · PR #41
🆔 6bc09611
👥 Por: 241solutions (2 commits)
📝 Commits:
• fc4086f — merge: refund type frontend + label mapping wallet.
• d832c1d — fix(auth): pre-flight JWT expiry + abort-all 401 cascadeConclusão / Falha (enxutas e auto-contidas, com duração; falha traz o erro):
✅ Deploy concluído
Deploy: sweepapex-useradmin-front
Projeto: Sweepapex
🌐 Instance: production
🏷️ fc4086f
🆔 dca185c6
⏱️ 22sLinha 🌿 Branch:: mostra sempre a branch do deploy; anexa · PR #N quando um PR
é detectado (campo pr_number ou trailer (#N) no subject do commit). Não há
mais o ⚠️ direct push — quando não há PR, mostra só a branch.
Para PR deploys com TTL ativo, aparece uma linha ⏰ Auto-destroy em Xh no início.
Verbose errors (falha de deploy)
Com extras.verbose_errors: true (opt-in, default false), numa falha o Runner
envia uma segunda mensagem com o log completo do erro:
- Resumo com o erro extraído (até 800 chars)
- Log completo do erro (até 3800 chars)
As duas mensagens são correlacionadas pelo mesmo deploy_id. Exige a classe
deploy ligada (enabled: true).
notify:
enabled: true
extras:
verbose_errors: trueCanais nomeados (config.yml)
Além da config global de deploy (seção anterior), o config.yml pode declarar canais nomeados em notify.channels:. Cada app referencia esses canais pelo nome — as credenciais ficam só aqui, nunca no .deploy.yml do repo.
# /opt/runner/config.yml
notify:
# ... config global (channel/enabled/extras) ...
channels:
time_telegram:
type: telegram
bot_token: "123456:ABC-DEF..."
chat_id: "-100123456789"
ops_email:
type: smtp
host: smtp.exemplo.com
port: 587
username: deploys@exemplo.com
password: "{SMTP_PASSWORD}"
from: "deploys@exemplo.com"
to: "ops@exemplo.com"
meu_webhook:
type: webhook
base_headers:
Authorization: "Basic {BASE64_CREDS}"Tipos de canal suportados: telegram, webhook, smtp (e-mail).
Notificações por app (.deploy.yml)
Cada app pode declarar, no próprio .deploy.yml, uma lista de subscrições que referenciam canais nomeados do config.yml. É aditivo à config global — não substitui as notificações de classe deploy do servidor.
# .deploy.yml da app
project: meu-app
image: caddy:2-alpine
port: 80
notify:
- on: [deploy_success, deploy_failure]
when: "{{::instance_name}} == production"
channel: time_telegram
template: "{{::repository_name}} v{{::deploy_version}} — {{::deploy_result_status}}"
- on: [deploy_rollback]
channel: meu_webhook
request:
method: POST
url: "https://hooks.exemplo.com/{{::TOKEN}}"
body: '{"msg":"{{::repository_name}} rollback"}'Campos:
| Campo | Descrição |
|---|---|
on: |
Eventos: deploy_started, deploy_success, deploy_failure, deploy_rollback |
when: |
Condição opcional {{::var}} == valor / != valor. Sem when, dispara sempre |
channel: |
Nome de um canal em notify.channels: do config.yml |
template: |
Corpo da mensagem (canais telegram/smtp) |
subject: |
Assunto do e-mail (opcional, smtp) |
request: |
method/url/headers/body (canal webhook) |
template, subject: e os campos de request: usam a mesma sintaxe {{::var}} de secrets:. Além do contexto do deploy (repository_name, deploy_result_status, deploy_version, deploy_error, instance_name, instance_domain, event, deploy_id, project), dá pra referenciar variáveis do environment:/secrets: do próprio app — resolvidas do bundle criptografado no momento do deploy. Valores reais de secret nunca ficam gravados no manifesto.
Uma subscrição com canal inexistente, mal configurado ou que falhe no envio é apenas logada — nunca bloqueia o deploy (fire-and-forget).
Isso é útil quando apps de projetos diferentes precisam notificar chats/e-mails distintos no mesmo servidor.
Comandos disponíveis
# Setup interativo
runner notify setup --channel telegram
runner notify setup --channel webhook
runner notify setup --channel both
# Enviar mensagem de teste
runner notify test
# Ver status dos canais configurados
runner notify statusO que pode dar errado
Telegram não envia após configuração manual
O chat_id de grupos e canais é negativo (começa com -100...). Valores positivos são de DMs. Verifique o sinal.
Teste direto:
curl -s "https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=<CHAT_ID>&text=teste"A resposta inclui "ok": true se funcionou, ou "description": "..." com o erro.
Bot em grupo não recebe mensagens
Por padrão, bots em grupos só recebem mensagens diretas (/comando). Para receber todas as mensagens e detectar o chat_id:
- No @BotFather:
/mybots> seu bot > Bot Settings > Group Privacy > Turn off - Remova e readicione o bot ao grupo
Webhook retorna erro de SSL
O Runner exige HTTPS com certificado válido. Certificados auto-assinados não são aceitos. Use um domínio com certificado Let's Encrypt ou similar.
App pausada não notifica
O evento app_paused é disparado apenas no momento da pausa. Se a app já estava pausada quando o Runner iniciou, não há notificação retroativa.
Links relacionados
- Referência config.yml — todos os campos da seção
notify: - GitOps mode — como o cron fetch --deploy funciona
- Pause e unpause — evento
app_pausede auto-pause