---
title: "CloudWatch の電話アラート：EventBridge で Lambda 不要"
description: "SNS は電話をかけられず、ペイロードも扱いにくい。CloudWatch アラームを EventBridge 経由で着信 Webhook へ流す設定・フィルタ・注意点。"
date: 2026-10-16
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - AWS
  - CloudWatch
  - EventBridge
  - 電話アラート
  - Webhook
  - オンコール
---

# CloudWatch アラームの電話アラート：EventBridge からスマホへ、Lambda は不要

CloudWatch アラームはあなたに電話をかけられません。組み込みのアクションは SNS、Auto Scaling、EC2、Systems Manager だけで、SNS の行き着く先はメール、SMS、あるいは見張っていなければ気づけないキューです。実際に電話を鳴らすチュートリアルは、Lambda 関数と Twilio アカウントを組み合わせています。どちらも不要です。EventBridge にアラームの状態変化を受け取らせ、小さな JSON ボディに整形して、Echobell の着信チャンネルへ POST すれば済みます。

この記事では経路全体を扱います。チャンネル、EventBridge ルール、入力トランスフォーマー、着信音に意味を持たせ続けるフィルタ、そして SNS を Webhook に直接向けると動きそうに見えてほとんど動かない理由です。

## 定番の SNS 経路が扱いにくい理由

よくある助言は「アラーム → SNS トピック → サブスクライバー」です。電話にするには、これに 3 つの問題があります。

- **SNS には音声プロトコルがありません。** サブスクライバーの種類はメール、SMS、モバイルプッシュ、SQS、Lambda、Firehose、HTTP(S) です。SMS は見逃しやすく、おやすみモードにも阻まれます。音声通話は自分で作るしかありません。
- **HTTPS のメッセージは文字列の中の文字列です。** SNS はエンベロープを `Content-Type: text/plain` で配信し、アラーム本体は JSON ドキュメントとしてシリアライズされ、エンベロープの `Message` フィールドに入っています。`AlarmName` や `NewStateValue` のようにフィールドを読む Webhook 受信側には、中身の見えないひとかたまりにしか見えません。
- **エンベロープが大きい。** 署名、署名証明書の URL、配信停止リンクだけで、アラームの文面が始まる前に 1 キロバイトを優に超えます。Echobell は通知本文を 1500 バイトで切り詰めるため、「ボディ全部を出す」テンプレートは、肝心な部分が始まるところでちょうど切れてしまいます。

メッセージを取り出して転送する Lambda を置けば SNS でも動かせます。しかし CloudWatch は、アラームの状態変化をすべてきれいな JSON イベントとして EventBridge にすでに発行しています（アクションの設定は不要）。そして EventBridge 自身が、整形して HTTPS エンドポイントを呼べます。

## 必要なもの

- AWS アカウントと、EventBridge のルール、コネクション、API destination を作成する権限
- Echobell のインストール（[App Store](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-cloudwatch-alarm-phone-call-alerts-ja&mt=8) / [Google Play](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid)）
- 既存の CloudWatch アラームを少なくとも 1 つ
- 10 分

あなた側でインバウンド通信を受け付ける必要はありません。アウトバウンドのリクエストは EventBridge が送ります。

## ステップ 1 — 鳴るチャンネルを作る

Echobell でチャンネルを作り、通知タイプを **着信（Calling）** にします。着信チャンネルはプッシュではなく着信として振る舞うので、iOS の集中モードやおやすみモードを突破します。

ステップ 3 のトランスフォーマーは `alarm`、`state`、`reason`、`region`、`link` のフィールドを持つフラットなボディを送るので、テンプレートは短く済みます。

```
タイトル: {{state}}: {{alarm}}
本文: {{reason}}
```

詳細設定で **リンクテンプレート** を設定しておくと、通知をタップしてコンソールでアラームを開けます。

```
{{link}}
```

チャンネル詳細画面から、チャンネルの Webhook URL をコピーします。

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

## ステップ 2 — コネクションと API destination を作る

EventBridge は **API destination** を通して外部の HTTPS エンドポイントを呼び出し、それには認証情報を持つ **connection** が必要です。Echobell は URL 内の秘密トークンで認証するので、コネクションは形式的なものです。ただし、これがないと EventBridge は destination を作らせてくれません。

1. **EventBridge → Integration → API destinations → Connections → Create connection** を開く
2. **Name**：`echobell`
3. **Authorization type**：**API key**
4. **API key name**：`x-source`、**Value**：`eventbridge`
5. 作成したら **API destinations → Create API destination** へ
6. **Name**：`echobell-calling`
7. **API destination endpoint**：ステップ 1 の Echobell チャンネル URL
8. **HTTP method**：`POST`
9. **Invocation rate limit**：毎秒 `1` 回で十分で、安全弁にもなります
10. **Connection**：`echobell`

API キーのヘッダーは毎回のリクエストで送られます。トークンの手前にもう一枚ゲートが欲しければ、Echobell のチャンネル条件で確認できます。

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

## ステップ 3 — ルールと入力トランスフォーマーを作る

アラームと同じリージョンで **EventBridge → Rules → Create rule** に進みます。ルールはリージョン単位です。`eu-west-1` のアラームは `us-east-1` のルールからは見えません。

**default** イベントバスと **event pattern** ルールを使います。次のパターンは、`ALARM` に遷移したすべてのアラームに一致します。

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

ターゲットには **EventBridge API destination** を選び、`echobell-calling` を指定します。実行ロールはコンソールに新規作成させてください。この destination に対する `events:InvokeApiDestination` だけが付与され、それ以外は何も付きません。

続けて **Additional settings → Configure target input → Input transformer** を開きます。

**Input path**

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

**Template**

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

EventBridge は結果を `application/json` として送るので、Echobell は `{{alarm}}` や `{{state}}` を直接読めます。

## ステップ 4 — 実際のイベントの形を知る

入力パスは、CloudWatch が発行するイベントから値を読みます。要点だけに絞るとこうです。

```json
{
  "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" }
  }
}
```

テンプレートを編集する前に知っておきたいことがあります。

- **アラームの情報は `detail` の下にあります。** `$.alarmName` は何にも一致せず、`$.detail.alarmName` なら一致します。
- **`state.reason` は自動生成されるテキストです。** データポイントとしきい値が書かれていて、午前 3 時にまさに聞きたい内容です。
- **アラーム名はそのまま挿入されます。** アラーム名や reason にダブルクォートが入ると、トランスフォーマーが作る JSON が壊れます。アラーム名には英数字、ハイフン、アンダースコアだけを使ってください（そもそも良い習慣です）。さもないと呼び出しは 400 で失敗します。
- **リンクにはシンプルな名前が必要です。** スペースや `#` を含む名前だとディープリンクが正しく動きません。コンソールの検索なら見つかります。

## ステップ 5 — 電話に値するものまで絞り込む

`ALARM` のたびに鳴るアラームチャンネルは、自分に無視することを教え込む最短ルートです。絞り込みは 2 か所で行います。

**EventBridge のパターンで。** 何かが送られる前に働きます。

| 目的 | パターンの変更 |
| --- | --- |
| 本番のアラームだけ | `detail` の中に `"alarmName": [{ "prefix": "prod-" }]` |
| 緊急でないものを除外 | 名前を `warn-…` にして `"alarmName": [{ "anything-but": { "prefix": "warn-" } }]` を使う |
| 特定のアラームだけ | `"alarmName": ["prod-api-5xx", "prod-db-cpu"]` |
| 本物のアラームだけ、テスト状態は除外 | `"state": { "value": ["ALARM"] }` を維持。`INSUFFICIENT_DATA` のイベントは決して一致しません |
| 1 アカウントまたは 1 リージョンだけ | トップレベルに `"account": ["123456789012"]` または `"region": ["us-east-1"]` を追加 |

**Echobell 側で。** 時間帯のように、パターンで表現できないものにはチャンネル条件を使います。

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

Echobell の時刻変数は UTC なので、まず自分のタイムゾーンから換算してください。変数の全一覧は[条件リファレンス](/ja/docs/conditions)にあります。

## ステップ 6 — 復旧通知には静かな入口を用意する

`"value": ["OK"]` の 2 つ目のルールを作れば、鳴らさずに問題が解消したことを知らせられます。**時間指定（Time Sensitive）**（または通常）タイプの 2 つ目の Echobell チャンネルと、それに向けた 2 つ目の API destination を作り、同じ入力トランスフォーマーを再利用します。タイトルテンプレート `{{state}}: {{alarm}}` は、すでに「OK: prod-api-5xx」と読めます。

| ルール | 状態 | チャンネルタイプ | 挙動 |
| --- | --- | --- | --- |
| `alarms-to-call` | `ALARM`、`prod-` プレフィックス | 着信 | 集中モードを突破して鳴る |
| `alarms-warn` | `ALARM`、`warn-` プレフィックス | 時間指定 | ロック画面に出るが鳴らない |
| `alarms-recovered` | `OK` | 通常 | 普通のプッシュ |

## 信用する前にテストする

本番が壊れるのを待たないでください。アラームを狙った状態に強制します。

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

アラームは次の評価で本来の状態に戻りますが、状態変化イベントは本物で、ルールを通ります。Echobell 側だけをテストしたいなら、チャンネルを直接叩きます。

```bash
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/"}'
```

`curl` では鳴るのにアラームのテストで鳴らないなら、問題はチャンネルではなく、ルール、トランスフォーマー、または destination にあります。

## SNS やメールからの着信化は？

すでにあちこちに SNS トピックがあり、EventBridge には触れたくない場合、ほかに 2 つの経路があります。

- **SNS → Lambda → Webhook。** 数行のコードで `Message` をパースし、同じ Echobell URL にフラットなボディを POST します。動きますが、関数、ロール、そのログを自分で抱えることになります。
- **SNS → メール → Echobell のメールトリガー。** チャンネルの[メールトリガーアドレス](/ja/docs/email-trigger)をトピックに購読させます。アラームはメールとして届き、テンプレートの `{{subject}}` と `{{text}}` が通知になります。設定は最も少ない一方で、SNS のメール購読には一度だけ確認リンクのクリックが必要で、テキストは整形されません。

Echobell の Webhook URL を HTTPS エンドポイントとしてトピックに直接購読させるのは避けるべき経路です。確認リクエストが通知として届き、その後のアラームはすべて、前述の長いエンベロープになります。

## インシデント中に効いてくる制限

- **ボディ 1 MiB。** Echobell は 1 MiB を超えるトリガーボディを HTTP 413 で拒否します。変換後のアラームは数百バイトなので、ここで当たることはありません。
- **トークンあたり毎分 120 リクエスト。** それを超えるとトリガーは `429` と `RATE_LIMIT_EXCEEDED`、`Retry-After` を返します。destination の毎秒 1 回というレート制限が範囲内に収めてくれますし、EventBridge は配信できなかったイベントを再試行します。
- **通知本文 1500 バイト。** それより長い描画結果は切り詰められます。`state.reason` は 1〜2 文なので収まります。

## この構成で得られないもの

- **確認（ack）はありません。** 電話に出ても CloudWatch には何も伝わらず、他のサブスクライバーを黙らせることもありません。
- **エスカレーションもローテーションもありません。** チャンネルを購読している全員に電話がかかり、「次の人」はいません。
- **アラーム自体を超える重複排除はありません。** `OK → ALARM` を数分おきに繰り返すフラッピングのアラームは、毎回鳴ります。アラーム側で評価期間を長くするか `DatapointsToAlarm` を設定して直してください。
- **双方向同期はありません。** 原因を直しても、端末側は何も消えません。

これらが致命的なら、必要なのは本格的なオンコールプラットフォームです。必要なのが「本番のアラームが発火したら鳴らしてくれ」だけなら、コンソールの数画面で済みます。

## トラブルシューティング

**アラームは発火したのに何も届かない。** ルールの **Monitoring** タブを開いてください。`Invocations` はパターンが一致したか、`FailedInvocations` は配信が失敗したかを示します。アラームと別のリージョンにあるルールは決して一致しません。

**ルールは一致するが呼び出しが 400 で失敗する。** トランスフォーマーが不正な JSON を生成しています。アラーム名や reason にクォートやバックスラッシュがないか探してください。

**通知が空白。** テンプレートが、トランスフォーマーの送っていないフィールドを読んでいます。ボディはフラットです。`{{detail.alarmName}}` ではなく `{{alarm}}` と書きます。

**1 つのアラームで繰り返し鳴る。** アラームがフラッピングしています。評価期間を延ばすか、より多くのデータポイントを要求してください。これは Echobell ではなく CloudWatch の設定です。

**想定外のアラームで鳴る。** パターンに `prefix` フィルタの抜けがないか確認してください。また、コンポジットアラームや、他のツールが作成したアラーム（たとえば Auto Scaling ポリシー）も状態変化を発行することを忘れないでください。

## よくある質問

### CloudWatch はネイティブに電話番号へかけられますか？

いいえ。CloudWatch のアラームアクションは SNS 通知、Auto Scaling、EC2 アクション、Systems Manager です。音声通話には外部サービスが必要で、プロバイダー経由で発信する Lambda、オンコールプラットフォーム、または Echobell のような着信 Webhook を使います。

### SNS トピックは必要ですか？

いいえ。アラームにアクションがあるかどうかに関係なく、CloudWatch は自らアラームの状態変化イベントを EventBridge に送ります。アラームのアクション一覧は空のままで構いません。

### CloudWatch のアラームはおやすみモードを突破しますか？

着信型アラートとして届いた場合だけです。着信タイプの Echobell チャンネルは着信として振る舞うため、iOS の集中モードやおやすみモードが通します。SMS や通常のプッシュは通りません。

### Webhook URL を直接 SNS に購読させてはだめですか？

SNS は `text/plain` で送り、アラームは `Message` 文字列の中にシリアライズされるため、テンプレートは個々のフィールドを読めず、エンベロープは切り詰められるほど長くなります。さらに SNS は最初に購読確認を送り、それが通知として届いてしまいます。

### AWS で費用はかかりますか？

アラームの状態変化イベントが default イベントバスに届く分は無料です。API destination の呼び出しはリクエスト単位で課金されます。健全なアラーム群の量なら無視できる程度ですが、お使いのリージョンの最新の EventBridge 料金を確認してください。

### アラームごとに別の人へ電話できますか？

できます。受け手ごとに 1 チャンネル、チャンネルごとに 1 ルールを作り、各自が好みの通知タイプで購読します。確認機能がないので、「着信」で購読した全員に電話がかかります。

### 複数のリージョンやアカウントのアラームは？

ルールはリージョン単位なので、使う各リージョンにルールを作ります（同じ API destination の定義をリージョンごとに繰り返します）。複数アカウントの場合は、イベントを 1 つのアカウントの中央バスに集め、そこにルールを置きます。

## まとめ

経路は 5 つの部品です。着信チャンネル、それを指すコネクションと API destination、人を起こす価値のあるアラームだけに一致するルール、イベントをフラットにする入力トランスフォーマー、そして何かを頼る前に実行する `set-alarm-state` の強制テスト。ここに書いた残りはすべて、1 か月後もその着信音が意味を保てるよう、十分に狭く保つための話です。

[iPhone 版 Echobell をダウンロード](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-cloudwatch-alarm-phone-call-alerts-ja&mt=8)、または [Google Play で入手](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid)して、`set-alarm-state` のテストを実行してください。

## 関連記事

- [チャンネル条件リファレンス](/ja/docs/conditions)
- [Webhook 連携ガイド](/ja/docs/webhook)
- [メールトリガーガイド](/ja/docs/email-trigger)
- [Sentry の電話アラート](/ja/blog/sentry-phone-call-alerts)
- [API ダウンタイム時の電話アラート](/ja/blog/phone-call-alerts-api-downtime)
- [開発者のためのアラート疲れ解消ガイド](/ja/blog/fix-alert-fatigue-developer-guide)
- [重要なアラートで iOS 集中モードを突破する](/ja/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)
