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.yml no 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_success foram substituídas. Configs antigos não quebram (chaves desconhecidas são ignoradas), mas não migram sozinhosenabled cai no default true e os extras em false. 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.jsonl registra 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, extras false): 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

  1. Abra o Telegram e busque @BotFather
  2. Envie /newbot e siga as instruções
  3. Copie o token (formato: 123456789:ABCdefGHI...)

2. Obter o chat_id

Para DM ou grupo — use o setup interativo:

runner notify setup --channel telegram

O 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 test

O 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 deploy
  • pr_number — PR detectado (squash trailer (#N), merge commit, ou runner add --pr N)
  • expires_at — TTL do PR preview (ISO 8601), quando aplicável
  • last_commit — autor, hash e subject do commit real mais novo do deploy
  • commit_authors — autores únicos do range (multi-commit)
  • commits_count — total de commits reais reconhecidos no deploy
  • commits — lista dos commits reais do range (newest-first, capada), com os auto-commits chore: update encrypted secrets filtrados

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 cascade

Conclusã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
⏱️ 22s

Linha 🌿 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:

  1. Resumo com o erro extraído (até 800 chars)
  2. 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: true

Canais 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 status

O 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:

  1. No @BotFather: /mybots > seu bot > Bot Settings > Group Privacy > Turn off
  2. 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.

By Borlot.com.br on 27/05/2026