Alarmes CloudWatch por ligação sem Lambda nem Twilio

O SNS não liga para um telefone. Envie os alarmes do CloudWatch pelo EventBridge a um webhook de ligação: configuração, filtros e armadilhas.

Atualizado

Sumário

Um alarme do CloudWatch não consegue ligar para o seu telefone. As únicas ações embutidas são SNS, Auto Scaling, EC2 e Systems Manager, e o SNS termina em e-mail, SMS ou numa fila que você precisa estar olhando. Os tutoriais que de fato fazem o telefone tocar montam uma função Lambda e uma conta Twilio. Dá para pular os dois: deixe o EventBridge receber a mudança de estado do alarme, remodelá-la num pequeno corpo JSON e enviá-la por POST a um canal de ligação do Echobell.

Este guia cobre o caminho inteiro: o canal, a regra do EventBridge, o input transformer, os filtros que mantêm o toque relevante e por que apontar o SNS direto para o webhook parece que deveria funcionar e, na maioria das vezes, não funciona.

Por que o caminho usual pelo SNS fica complicado

O conselho padrão é "alarme → tópico SNS → assinante". Para uma ligação telefônica, isso tem três problemas:

  • O SNS não tem protocolo de voz. Os tipos de assinante são e-mail, SMS, push móvel, SQS, Lambda, Firehose e HTTP(S). SMS é fácil de perder e é bloqueado pelo Não Perturbe; uma chamada de voz exige algo que você mesmo monte.
  • A mensagem HTTPS é uma string dentro de uma string. O SNS entrega seu envelope com Content-Type: text/plain, e o alarme em si é um documento JSON serializado no campo Message do envelope. Um receptor de webhook que lê campos — AlarmName, NewStateValue — encontra apenas um bloco opaco.
  • O envelope é grande. Entre a assinatura, a URL do certificado de assinatura e o link de cancelamento, o envelope adiciona bem mais de um kilobyte antes de o texto do seu alarme começar. O Echobell trunca o corpo de uma notificação em 1500 bytes, então um template "despeje o corpo inteiro" corta justamente onde a parte útil começa.

Dá para fazer o SNS funcionar com uma Lambda que desembrulha a mensagem e a encaminha. Mas o CloudWatch já publica toda mudança de estado de alarme no EventBridge como um evento JSON limpo — sem nenhuma ação para configurar — e o EventBridge consegue remodelá-lo e chamar um endpoint HTTPS por conta própria.

O que você precisa

  • Uma conta AWS e permissão para criar regras, conexões e API destinations no EventBridge
  • Echobell instalado (App Store / Google Play)
  • Pelo menos um alarme do CloudWatch existente
  • Dez minutos

Nada do seu lado precisa aceitar tráfego de entrada. Quem faz a requisição de saída é o EventBridge.

Passo 1 — Crie um canal que toca

No Echobell, crie um canal e defina o tipo de notificação como Ligação. Um canal de ligação se comporta como uma chamada recebida em vez de um push, então atravessa o modo Foco e o Não Perturbe do iOS.

O transformer do passo 3 envia um corpo plano com os campos alarm, state, reason, region e link, então os templates ficam curtos:

Título: {{state}}: {{alarm}}
Corpo: {{reason}}

Configure o template de link nas configurações avançadas para que tocar na notificação abra o alarme no console:

{{link}}

Copie a URL do webhook na tela de detalhes do canal:

https://hook.echobell.one/t/<channel-token>

Passo 2 — Crie a connection e a API destination

O EventBridge chama endpoints HTTPS externos por meio de uma API destination, que precisa de uma connection que carregue a autenticação. O Echobell autentica pelo token secreto na URL, então a connection é só formalidade — mas o EventBridge não cria uma destination sem ela.

  1. Abra EventBridge → Integration → API destinations → Connections → Create connection
  2. Name: echobell
  3. Authorization type: API key
  4. API key name: x-source, Value: eventbridge
  5. Crie-a e depois vá em API destinations → Create API destination
  6. Name: echobell-calling
  7. API destination endpoint: a URL do seu canal Echobell do passo 1
  8. HTTP method: POST
  9. Invocation rate limit: 1 por segundo é mais que suficiente e funciona como válvula de segurança
  10. Connection: echobell

O cabeçalho da API key é enviado em toda requisição. Você pode verificá-lo numa condição de canal do Echobell se quiser uma segunda barreira antes do token:

header["x-source"] == "eventbridge"

Passo 3 — Crie a regra e o input transformer

Vá em EventBridge → Rules → Create rule na mesma região dos seus alarmes. Regras são regionais: um alarme em eu-west-1 é invisível para uma regra em us-east-1.

Use o event bus default e uma regra de event pattern. Este padrão casa com todo alarme que passa para ALARM:

{
  "source": ["aws.cloudwatch"],
  "detail-type": ["CloudWatch Alarm State Change"],
  "detail": {
    "state": { "value": ["ALARM"] }
  }
}

Como alvo, escolha EventBridge API destination e selecione echobell-calling. Deixe o console criar uma nova execution role — ela concede events:InvokeApiDestination para esta destination e mais nada.

Depois expanda Additional settings → Configure target input → Input transformer:

Input path

{
  "alarm": "$.detail.alarmName",
  "state": "$.detail.state.value",
  "reason": "$.detail.state.reason",
  "region": "$.region",
  "time": "$.time"
}

Template

{
  "alarm": "<alarm>",
  "state": "<state>",
  "reason": "<reason>",
  "region": "<region>",
  "time": "<time>",
  "link": "https://console.aws.amazon.com/cloudwatch/home?region=<region>#alarmsV2:alarm/<alarm>"
}

O EventBridge envia o resultado como application/json, e é isso que permite ao Echobell ler {{alarm}} e {{state}} diretamente.

Passo 4 — Saiba como o evento realmente se parece

O input path lê do evento que o CloudWatch publica. Resumido, ele é assim:

{
  "version": "0",
  "id": "c4c1c1c9-6542-e61b-6ef0-8c4d36933a92",
  "detail-type": "CloudWatch Alarm State Change",
  "source": "aws.cloudwatch",
  "account": "123456789012",
  "time": "2026-10-16T03:12:44Z",
  "region": "us-east-1",
  "resources": ["arn:aws:cloudwatch:us-east-1:123456789012:alarm:prod-api-5xx"],
  "detail": {
    "alarmName": "prod-api-5xx",
    "state": {
      "value": "ALARM",
      "reason": "Threshold Crossed: 1 out of the last 1 datapoints [42.0] was greater than the threshold (10.0).",
      "timestamp": "2026-10-16T03:12:44.000+0000"
    },
    "previousState": { "value": "OK" },
    "configuration": { "description": "API 5xx errors on the prod load balancer" }
  }
}

O que vale saber antes de editar um template:

  • O alarme fica sob detail. $.alarmName não casa com nada; $.detail.alarmName casa.
  • state.reason é texto gerado. Ele cita o datapoint e o limiar, que é exatamente o que você quer ouvir às 3 da manhã.
  • Nomes de alarme são inseridos crus. Uma aspa dupla dentro do nome ou do reason de um alarme quebraria o JSON que o transformer produz. Use apenas letras, dígitos, hifens e underscores nos nomes de alarme — o que já é boa prática — ou a ligação falhará com um 400.
  • O link precisa de um nome simples. Um nome com espaços ou # não vai levar ao lugar certo; a busca do console ainda o encontra.

Passo 5 — Estreite até o que merece uma ligação

Um canal que toca a cada ALARM é a forma mais rápida de aprender a ignorá-lo. Filtre em dois lugares.

No padrão do EventBridge, que roda antes de qualquer coisa ser enviada:

ObjetivoMudança no padrão
Só alarmes de produção"alarmName": [{ "prefix": "prod-" }] dentro de detail
Ignorar o que não é urgenteNomeie-os warn-… e use "alarmName": [{ "anything-but": { "prefix": "warn-" } }]
Só alarmes específicos"alarmName": ["prod-api-5xx", "prod-db-cpu"]
Só alarmes de verdade, não estados de testeMantenha "state": { "value": ["ALARM"] } — um evento INSUFFICIENT_DATA nunca casa
Só uma conta ou regiãoAdicione "account": ["123456789012"] ou "region": ["us-east-1"] no nível superior

No Echobell, use uma condição de canal para o que o padrão não consegue expressar, como uma janela de horário:

state == "ALARM" && (hour >= 17 || hour < 9)

As variáveis de hora do Echobell são em UTC, então converta do seu fuso primeiro. A referência de condições lista todas as variáveis.

Passo 6 — Dê às recuperações uma porta mais silenciosa

Uma segunda regra com "value": ["OK"] pode avisar que o problema passou, sem tocar. Crie um segundo canal Echobell do tipo Urgente (Time Sensitive) (ou normal), uma segunda API destination apontando para ele e reutilize o mesmo input transformer. O template de título {{state}}: {{alarm}} já se lê como "OK: prod-api-5xx".

RegraEstadoTipo de canalComportamento
alarms-to-callALARM, prefixo prod-LigaçãoToca atravessando o Foco
alarms-warnALARM, prefixo warn-UrgenteTela bloqueada, sem tocar
alarms-recoveredOKNormalPush comum

Teste antes de confiar

Não espere a produção quebrar. Force um alarme para o estado que você quer:

aws cloudwatch set-alarm-state \
  --alarm-name prod-api-5xx \
  --state-value ALARM \
  --state-reason "Manual test of the Echobell path"

O alarme volta ao estado real na próxima avaliação, mas o evento de mudança de estado é real e passa pela sua regra. Para testar apenas o lado do Echobell, dispare o canal diretamente:

curl -X POST https://hook.echobell.one/t/<channel-token> \
  -H 'Content-Type: application/json' \
  -d '{"alarm":"prod-api-5xx","state":"ALARM","reason":"Manual test","region":"us-east-1","link":"https://console.aws.amazon.com/cloudwatch/"}'

Se o curl toca e o teste do alarme não, o problema está na regra, no transformer ou na destination — não no canal.

E o SNS com e-mail para ligação?

Se você já tem um tópico SNS em todo lugar e não quer mexer no EventBridge, existem outros dois caminhos:

  • SNS → Lambda → webhook. Poucas linhas de código leem Message e depois enviam um corpo plano para a mesma URL do Echobell. Funciona, mas agora você é dono de uma função, de uma role e dos logs dela.
  • SNS → e-mail → gatilho de e-mail do Echobell. Assine o endereço do gatilho de e-mail do canal no tópico. O alarme chega como e-mail, e {{subject}} e {{text}} nos seus templates viram a notificação. É a configuração mais enxuta de todas, com a ressalva de que as assinaturas de e-mail do SNS exigem um clique de confirmação único e o texto não é remodelado.

Assinar a URL do webhook do Echobell diretamente no tópico como endpoint HTTPS é o caminho a evitar: a requisição de confirmação chega como notificação, e todo alarme depois disso é o envelope longo descrito acima.

Limites que importam durante um incidente

  • Corpo de 1 MiB. O Echobell rejeita corpos de disparo acima de 1 MiB com HTTP 413. Um alarme transformado tem algumas centenas de bytes, então você não vai chegar nisso aqui.
  • 120 requisições por minuto por token. Acima disso, o gatilho responde 429 com RATE_LIMIT_EXCEEDED e um Retry-After. O limite de 1 por segundo da destination te mantém abaixo, e o EventBridge refaz o envio de eventos que não conseguiu entregar.
  • 1500 bytes de corpo da notificação. Renderizações mais longas são truncadas. state.reason é uma ou duas frases e cabe.

O que esta configuração não oferece

  • Sem confirmação. Atender a ligação não diz nada ao CloudWatch e não silencia os outros assinantes.
  • Sem escalonamento nem escala. Todos os inscritos no canal são chamados; ninguém é chamado depois.
  • Sem deduplicação além do alarme. Um alarme instável que vai de OK → ALARM a cada poucos minutos vai tocar toda vez. Resolva com períodos de avaliação mais longos ou DatapointsToAlarm no próprio alarme.
  • Sem sincronização bidirecional. Corrigir a causa não limpa nada no seu telefone.

Se isso for impeditivo, você quer uma plataforma de plantão completa. Se o que você precisa é "me ligue quando o alarme de produção disparar", são algumas telas do console.

Solução de problemas

O alarme disparou mas nada chegou. Abra a aba Monitoring da regra: Invocations mostra se o padrão casou, FailedInvocations se a entrega falhou. Regras numa região diferente da do alarme nunca casam.

A regra casa mas a ligação falha com um 400. O transformer produziu JSON inválido. Procure uma aspa ou barra invertida no nome ou no reason do alarme.

A notificação está em branco. O template está lendo um campo que o transformer não enviou. O corpo é plano: {{alarm}}, não {{detail.alarmName}}.

Toca repetidamente por um alarme. O alarme está instável. Aumente o período de avaliação ou exija mais datapoints; isso é uma configuração do CloudWatch, não do Echobell.

Toca por um alarme que você não esperava. Verifique no padrão se falta um filtro prefix e lembre que alarmes compostos e alarmes criados por outras ferramentas (políticas do Auto Scaling, por exemplo) também emitem mudanças de estado.

Perguntas frequentes

O CloudWatch consegue ligar para um número de telefone nativamente?

Não. As ações de alarme do CloudWatch são notificações SNS, Auto Scaling, ações do EC2 e Systems Manager. Uma chamada de voz exige um serviço externo: uma Lambda que disca por meio de um provedor, uma plataforma de plantão ou um webhook de ligação como o Echobell.

Preciso de um tópico SNS?

Não. O CloudWatch emite eventos de mudança de estado de alarme para o EventBridge por conta própria, tenha o alarme ações ou não. Você pode deixar a lista de ações do alarme vazia.

Um alarme do CloudWatch atravessa o Não Perturbe?

Só se chegar como alerta em formato de ligação. Um canal do Echobell do tipo ligação se comporta como uma chamada recebida, e isso o modo Foco e o Não Perturbe do iOS deixam passar. Um SMS ou um push comum, não.

Por que não simplesmente assinar a URL do webhook no SNS?

O SNS envia text/plain com o alarme serializado dentro de uma string Message, então o template não consegue ler campos individuais, e o envelope é longo o bastante para ser truncado. O SNS também envia primeiro uma confirmação de assinatura, que chegaria como notificação.

Isso tem custo na AWS?

Eventos de mudança de estado de alarme chegam ao event bus default sem cobrança; as invocações de API destination são cobradas por requisição. No volume de um conjunto saudável de alarmes, o valor é desprezível, mas confira os preços atuais do EventBridge para a sua região.

Posso ligar para pessoas diferentes para alarmes diferentes?

Pode. Crie um canal por público, uma regra por canal e deixe cada pessoa assinar com o tipo de notificação que quiser. Como não há confirmação, todos que assinaram com "ligação" são chamados.

E alarmes em várias regiões ou contas?

Regras são por região, então crie a regra em cada região que você usa (a mesma definição de API destination se repete por região). Para várias contas, envie os eventos a um bus central em uma conta e coloque a regra lá.

Fechando

O caminho tem cinco partes: um canal de ligação, uma connection e uma API destination apontando para ele, uma regra que casa apenas com os alarmes pelos quais vale acordar alguém, um input transformer que achata o evento e um teste forçado com set-alarm-state antes de confiar em qualquer coisa. Todo o resto aqui é sobre mantê-lo estreito o bastante para que, daqui a um mês, o toque ainda signifique alguma coisa.

Baixe o Echobell para iPhone ou pegue no Google Play e depois rode o teste com set-alarm-state.

Artigos relacionados