Alertas do GitLab pararam de aparecer no canal do Microsoft Teams? Este guia prático mostra como diagnosticar e corrigir falhas de notificações via Incoming Webhook, cobrindo políticas, erros HTTP (410/429/503), throttling, alterações de canal e boas práticas de prevenção.
Sintomas e contexto
Um canal do Teams que recebia avisos no fim das pipelines do GitLab deixou de mostrar mensagens. A pipeline continua a concluir com êxito e o job que envia o POST para o webhook não acusa falha aparente — ou os logs não registam o código de resposta da chamada HTTP. O problema pode estar em políticas do Teams, na validade do webhook, em limites de envio, em alterações no canal ou em incidentes do serviço.
Principais hipóteses de causa
Possível causa | Porque considerar |
---|---|
Incidente no serviço Teams/Connectors | Se o conector Incoming Webhook for impactado, o Teams pode não aceitar novas mensagens temporariamente, mesmo com pipelines a concluir. |
Política “Allow connectors to submit channel messages” desativada | Se desmarcada no Teams Admin Center, bloqueia todos os webhooks a nível organizacional. |
Webhook revogado ou canal/equipa alterado(a) | Apagar canal, arquivar equipa, remover permissões ou recriar o conector invalida a URL; respostas típicas: HTTP 410 (Gone) ou 404. |
Limites de throttling | Explosões de mensagens ou picos horários podem gerar HTTP 429 (Too Many Requests) ou 503/502 temporários. |
Proxy/Firewall corporativo | Bloqueios a *.webhook.office.com / outlook.office.com ou inspeção TLS podem interromper o POST. |
Alterações em políticas de apps | Políticas de permissão/instalação de apps por utilizador/equipa podem desativar “Conectores”. |
Diagnóstico rápido (resumo executável)
- Verifique estado do serviço no portal administrativo. Se houver incidente, registe o horário e avance para mitigação.
- No Teams Admin Center, confirme se Conectores e “Allow connectors to submit channel messages” estão ativos. Aguarde até 30 minutos para replicação.
- Recrie o Incoming Webhook no canal e substitua a URL no GitLab por uma variável segura (não em código).
- Teste manualmente com
curl
e registe o código HTTP e, se existir, o cabeçalhoRetry-After
. - Leia os logs do job CI/CD e garanta que o código de resposta é impresso de forma inequívoca.
- Confirme se o canal não está arquivado e se a equipa permite Conectores.
- Se houver 429/503, implemente retry com backoff exponencial e jitter.
Passo‑a‑passo detalhado
Confirmar estado do serviço
No Microsoft 365 Admin Center, veja Health → Service status e procure ocorrências relacionadas a Microsoft Teams, Connectors ou Incoming Webhook. Registe a janela temporal para correlacionar com a hora em que os alertas cessaram.
Rever políticas de conector e permissões
- No Teams Admin Center:
- Teams apps → Manage apps → Org‑wide settings → External apps: confirme Allow connectors to submit channel messages ativado.
- Se a organização usa políticas específicas, valide App permission policy e App setup policy ligadas a equipas/canais em questão.
- Após alterações, aguarde até 30 minutos para replicação.
Validar o próprio webhook
- No canal do Teams, abra Conector → Incoming Webhook → Configurar e gere uma nova URL.
- No GitLab, substitua a URL antiga por uma Variável de Ambiente (Settings → CI/CD → Variables) marcada como Masked e Protected.
- Guarde a URL num cofre de segredos (ex.: Key Vault/Secret Manager) e nunca a versione no repositório.
Testar envio manual
Envie um POST simples. Se funcionar manualmente mas falhar no GitLab, a causa está na runner (proxy, rede, TLS) ou no script.
Payload mínimo (texto):
{
"text": "Pipeline <PROJECT> concluída com sucesso ✅"
}
Exemplo com MessageCard (O365 Connector):
{
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"summary": "Resultado da pipeline",
"themeColor": "2EB886",
"title": "Deploy concluído",
"sections": [
{
"facts": [
{ "name": "Projeto", "value": "api-pagamentos" },
{ "name": "Pipeline", "value": "#1245" },
{ "name": "Branch", "value": "main" },
{ "name": "Duração", "value": "3m 18s" }
]
}
],
"potentialAction": [
{
"@type": "OpenUri",
"name": "Ver pipeline",
"targets": [{ "os": "default", "uri": "https://gitlab.example/pipelines/1245" }]
}
]
}
Teste com cURL (registando código HTTP e corpo):
# URL armazenada numa variável de ambiente segura: TEAMSWEBHOOKURL
echo '{ "text": "Teste de webhook do GitLab" }' > payload.json
captura código, cabeçalhos e corpo de resposta
curl -sS -D /tmp/headers.txt -o /tmp/body.txt
-H "Content-Type: application/json"
-X POST "\$TEAMS\WEBHOOK\URL"
\--data-binary @payload.json
status=\$?
http\_code=\$(grep -i "^HTTP/" /tmp/headers.txt | tail -1 | awk '{print \$2}')
retry\_after=\$(grep -i "^Retry-After:" /tmp/headers.txt | awk '{print \$2}')
echo "Exit code cURL: \$status"
echo "HTTP code: \${http\_code:-desconhecido}"
echo "Retry-After: \${retry\_after:-n/a}"
echo "Body:"
sed -n '1,200p' /tmp/body.txt
Teste equivalente em Python (requests
):
import os, json, time, random, requests
url = os.environ.get("TEAMS\WEBHOOK\URL")
payload = {"text": "Teste de webhook do GitLab via Python"}
for attempt in range(5):
r = requests.post(url, json=payload, timeout=10)
print("Tentativa", attempt+1, "→", r.status\_code, r.text)
if r.status\_code == 200:
break
if r.status\_code in (429, 503, 502):
ra = int(r.headers.get("Retry-After", 0))
backoff = max(ra, (2 \\ attempt)) + random.uniform(0, 1.0)
time.sleep(backoff)
else:
break </code></pre>
<h3>Examinar logs do CI/CD (GitLab)</h3>
<p>Garanta que o <em>job</em> imprime o <strong>código HTTP</strong> e não vaza a URL do webhook. Exemplo robusto:</p>
<pre><code class="language-yaml">notify:teams:
stage: notify
image: curlimages/curl:8.10.1
rules:
- if: '$CIPIPELINESOURCE == "push"'
variables:
WEBHOOKMASKED: "$TEAMSWEBHOOK_URL" # definir como masked/protected em Settings → CI/CD → Variables
script:
- echo '{ "text": "✅ Deploy concluído no ambiente $ENV para o commit $CICOMMITSHORT_SHA" }' > payload.json
- |
httpcode=$(curl -sS -o /tmp/body.txt -w "%{httpcode}" \
-H "Content-Type: application/json" \
-X POST "$WEBHOOK_MASKED" \
--data-binary @payload.json || echo "000")
echo "HTTPCODE=$httpcode"
# Opcional: inspecionar o corpo (não inclui segredos)
head -c 500 /tmp/body.txt || true
# Falhar o job em códigos considerados definitivos:
case "$http_code" in
200|204) exit 0;;
429|502|503) echo "Aviso: possível throttling. Recomendar retry/backoff."; exit 0;; # não falha a pipeline
*) echo "Falha ao publicar no Teams (HTTP $http_code)"; exit 1;;
esac
allow_failure: true
when: on_success
</code></pre>
<p><em>Dica:</em> ative <strong>artifacts:reports</strong> ou registe métricas para contar ocorrências de códigos ≠ 200.</p>
<h3>Verificar alterações no canal/equipa</h3>
<ul>
<li><strong>Canal arquivado</strong>: canais em equipas arquivadas ficam <em>read‑only</em> e mensagens do webhook podem não aparecer. Desarquive e teste.</li>
<li><strong>Permissões de Conectores</strong>: confirme que a equipa/canal permite Conectores nas definições.</li>
<li><strong>Rotatividade de proprietários</strong>: alterações de proprietários ou remoções podem invalidar conectores antigos; recrie o webhook.</li>
</ul>
<h3>Limites de throttling e boas práticas de retry</h3>
<p>Erros 429/503 são sinais de saturação temporária. A estratégia recomendada é <strong>exponencial com jitter</strong> e respeito ao cabeçalho <code>Retry-After</code> quando presente.</p>
<pre><code class="language-bash">retrywithbackoff() {
local max=5
local attempt=0
local base=2
while (( attempt < max )); do
httpcode=$(curl -sS -o /dev/null -w "%{httpcode}" -H "Content-Type: application/json" -X POST "$TEAMSWEBHOOKURL" --data-binary @payload.json || echo "000")
if [[ "$http_code" == "200" ]]; then
echo "Enviado com sucesso"; return 0
fi
if [[ "$http_code" =~ ^(429|502|503)$ ]]; then
sleep_time=$(( base attempt ))
jitter=$(awk -v s="$sleep_time" 'BEGIN{srand(); printf("%.2f", s*rand())}')
echo "HTTP $httpcode — aguardando $sleeptime+$jitter s"; sleep $(echo "$sleep_time+$jitter" | bc)
else
echo "Falha definitiva (HTTP $http_code)"; return 1
fi
((attempt++))
done
return 1
}
</code></pre>
<h3>Conectividade de rede e proxy</h3>
<ul>
<li>Garanta saída TCP 443 para <code>*.webhook.office.com</code> e, em ambientes legados, <code>outlook.office.com</code>.</li>
<li>Teste DNS e TLS a partir da <em>runner</em>:
<pre><code class="language-bash">nslookup webhook.office.com
curl -I https://webhook.office.com/
Em proxies corporativos, configure HTTP(S)PROXY/NOPROXY conforme a runner
Evite inspeção TLS que quebre SNI/cadeia de certificados. Se não for possível, inclua exceções para os FQDNs citados.
Mitigações rápidas
- Power Automate: acione um fluxo via HTTP request e poste no Teams; útil para aplicar lógica de retry e formatação.
- Bot Framework: construa um bot para postar cartões ricos e gerir autenticação.
- E‑mail para canal: obtenha o endereço do canal como contingência temporária (menor interatividade).
Tabela de códigos HTTP, diagnóstico e ação
Código | Interpretação | Ação recomendada |
---|---|---|
200/204 | Mensagem aceite | Verifique no canal. Se não aparecer, suspeite de política/canal arquivado ou latência de replicação. |
401/403 | Não autorizado/Proibido | Políticas de conectores ou URL inválida. Recrie o webhook e revise permissões. |
404 | Não encontrado | URL malformada/expirada ou canal removido. Gere nova URL. |
410 | Gone | Webhook revogado ou canal alterado. Crie um novo webhook e atualize as variáveis. |
429 | Too Many Requests (throttling) | Respeite Retry-After , implemente backoff, agregue mensagens e estabilize a cadência. |
500/502/503 | Erro temporário | Retente com backoff. Se persistente, verifique estado do serviço. |
000 | Falha do cliente (cURL/runner) | Rede, DNS, proxy ou TLS. Teste manual a partir da mesma sub-rede da runner. |
Exemplos práticos de payload e formatação
Mensagem resumida com facts:
{
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"summary": "Pipeline finalizada",
"themeColor": "0076D7",
"title": "Resultados do build",
"sections": [{
"facts": [
{"name": "Status", "value": "Sucesso"},
{"name": "Ambiente", "value": "Homolog"},
{"name": "Autor", "value": "ci-bot"}
]
}]
}
Dica: agregue eventos (ex.: um único cartão com múltiplos facts) para reduzir risco de 429 em picos.
Boas práticas para evitar novas falhas
- Observabilidade: registe métricas por código HTTP e alerte quando ≠ 200.
- Gestão de segredos: armazene a URL em cofre e use variáveis masked/protected. Rode a chave ao mínimo trimestralmente.
- Retry/backoff: exponencial com jitter e respeito a
Retry-After
. - Políticas revisadas: auditoria trimestral das políticas do Teams (apps, conectores, tenant).
- Runbook: documente passo a passo, proprietários e SLAs de restauro.
- Hardening de rede: allowlist de FQDNs relevantes e exclusão de inspeção TLS para domínios do webhook.
Checklist de prevenção (copiar/colar)
- [ ] Variáveis do webhook marcadas como masked/protected no GitLab
- [ ] Métricas e alarmes para 4xx/5xx
- [ ] Retry com backoff e jitter
- [ ] Revisão de políticas do Teams agendada (trimestral)
- [ ] Teste manual documentado (cURL + Python)
- [ ] Plano de contingência (Power Automate/Bot/E‑mail)
Perguntas frequentes
Um webhook “expira” com o tempo? Não por prazo fixo, mas pode tornar‑se inválido se o conector for removido/reconfigurado, se o canal for apagado/arquivado ou por alterações administrativas (ex.: políticas).
Consigo assinar as chamadas do webhook? O Incoming Webhook baseia‑se numa URL secreta e não disponibiliza HMAC nativo. Se precisa de assinatura/validação, interponha um serviço próprio (ex.: função serverless) que assine, rate‑limite e depois encaminhe para o Teams.
Posso enviar cartões avançados? O Incoming Webhook aceita JSON no formato de MessageCard (O365 Connector). Para experiências mais ricas e controlos avançados, considere Bots ou Power Automate.
Por que o job “passa” mesmo sem mensagem no Teams? Porque a pipeline avalia a sua própria execução; se não validar o código HTTP do POST, o job pode concluir com sucesso sem alertar sobre falha de notificação. Corrija o script para capturar e agir sobre o código.
Runbook sugerido (operacional)
- Identificar janela: quando parou? Há incidente ativo?
- Testar manual: cURL/Python a partir da rede da runner.
- Recriar webhook e atualizar variável no GitLab; executar pipeline de teste.
- Rever políticas no Teams Admin Center; aguardar replicação.
- Analisar logs de erro (410/401/403/429/503) e ajustar retry.
- Mitigar com contingência (Power Automate/Bot/E‑mail) se necessário.
- Prevenir com métricas, backoff, rotação de segredos e revisão trimestral.
Conclusão
Na maioria dos casos, a perda de notificações por webhook no Teams resolve‑se ao verificar políticas e recriar o conector, aliadas a testes controlados e retry com backoff. Com observabilidade e gestão adequada de segredos, o fluxo volta a ser confiável sem depender de suporte.