![[Twilio+Slack] Slack コマンドでの Twilio API コール: Lambda の Event モードによる非同期処理](https://images.ctfassets.net/ct0aopd36mqt/wp-thumbnail-3f316c20ff5f4f46d1a005e2ad976882/c71162912c717bb0ca7c9b4196a40df5/twilio.png?w=3840&fm=webp)
[Twilio+Slack] Slack コマンドでの Twilio API コール: Lambda の Event モードによる非同期処理
概要
以前の記事で、Slack のスラッシュコマンドから Twilio API を呼び出す際に、Lambda + SQS による非同期処理構成 を紹介しました。この構成は信頼性が高く、処理の失敗にも強い一方で、構築コストやコンポーネント数の面では少々重たく感じる場面もあります。
そこで今回は、「とにかく簡単に、軽量に Slack コマンドを連携したい」という方向けに、Lambda のみで完結する構成を紹介します。この構成のカギとなるのが、 Lambda の「Event モード(非同期呼び出し)」 です。
Slack × Lambda で 3 秒制限を回避するには
Slack のスラッシュコマンドには、リクエスト送信後 3 秒以内に成功を返す必要があるという制約があります。そのため、Twilio など外部 API を呼び出す処理を直接 Lambda 内で行うと、3 秒を超えてタイムアウトエラーになるリスクがあります。従来はこの問題を避けるために、Lambda → SQS → Lambda という構成で非同期処理を実現していました。
Event モードなら SQS なしでも非同期にできる
Lambda には InvocationType が存在し、デフォルトでは RequestResponse(同期)として実行されます。しかし、Lambda の InvocationType を Event に変更することで、Lambda を非同期に呼び出すことが可能です。
この仕組みを活用すれば、Slack に対しては即時に応答でき、Lambda 関数は Twilio API の呼び出しなど時間のかかる処理を裏で完了させるという形が実現できます。
この記事のゴール
本記事では、Slack のスラッシュコマンド /sms を実行した際に、Twilio API を通じて SMS を送信する処理を Lambda のみで非同期実行する構成を紹介します。Lambda の InvocationType を Event に設定することで、Slack の 3 秒制限を回避しつつ、サーバーレス構成をよりシンプルに保つ方法を解説します。
対象読者
- Slack コマンドから手軽に API を呼び出す仕組みを試したい方
- Event モードを利用した AWS Lambda による非同期処理を試してみたい方
- Twilio API(SMS 送信)を使った Slack 連携を短時間で構築したい方
想定ユースケース
- 社内からの緊急連絡を SMS で送信
 Slack から/sms +8190xxxxxxx 停電のため至急連絡くださいのように入力するだけ
- ステータス通知の SMS 中継
 Lambda を拡張して、API 経由で通知内容を入力できるようにすることで、Slack → SMS ブリッジとして活用
- PoC や検証用環境での利用
 シンプルな構成でサクッと動作確認を行いたいときに最適
参考
- https://docs.aws.amazon.com/ja_jp/lambda/latest/api/API_Invoke.html
- https://docs.aws.amazon.com/ja_jp/whitepapers/latest/security-overview-aws-lambda/lambda-invoke-modes.html
- https://repost.aws/ja/knowledge-center/api-gateway-invoke-lambda
全体構成図

この構成では、Slack スラッシュコマンドをトリガーに、Lamba(proxy) を介して本体の Lambda 関数が Event モード で非同期実行されます。非同期実行された Lambda(本体) は Twilio の SMS API を呼び出し、メッセージを送信します。Slack 側には即時に 200 OK が返されるため、3 秒ルールをクリアしつつ、バックグラウンドで安全に SMS 処理を行うことができます。
ポイントは以下のとおりです:
- Lambda(proxy) は Lambda(本体) を非同期(InvocationType: Event)で呼び出す
- Lambda(本体) は Slack の署名検証と Twilio API 呼び出しを行う
- SQS やステートフルなバッファは不要
これにより、構成の単純化と開発コストの削減が両立できます。
Twilio の初期設定: SMS 対応番号と認証情報を取得する
Slack から SMS を送信するには、Twilio 側で SMS 送信に対応した電話番号と、API を利用するための認証情報 (SID とトークン) を取得しておく必要があります。このセクションでは、Twilio コンソール上で行う初期設定について説明します。
ステップ 1: SMS 対応の電話番号を取得する
Twilio コンソール にアクセスし、左メニューから Phone Numbers > Manage > Buy a Number を選択します。 Capabilities で SMS にチェックを入れ、必要に応じて国 (例: United States - US) を指定して検索します。SMS 対応の電話番号を選択し、 Buy ボタンをクリックして購入します。

取得した電話番号は、後ほど AWS Lambda から SMS を送信する際の「送信元番号 (from) 」として使用します。番号は国番号付き (例: +81XXXXXXXXXX) の形式で控えておきましょう。
ステップ 2: API 認証情報 (SID・トークン) を確認する
Twilio API を利用するには、アカウントごとに発行される認証情報 (Account SID と Auth Token) が必要です。 Twilio コンソール にアクセスし、左メニューから Account Dashboard を選択します。 Account Info セクションに表示されている以下の情報を確認・コピーします。
- Account SID: アカウントの一意な識別子
- Auth Token: API 認証に使用する秘密鍵
 これらの情報は、後ほど AWS Lambda の環境変数として設定します。

Lambda(本体) 実装
非同期的に、 Twilio の SMS API を使ってメッセージを送信する Lambda 関数を実装します。
主な役割
- Slack からのリクエストに含まれる textパラメータをパース
- +8190xxxxxxxxの形式の電話番号と、送信メッセージ本文を抽出
- 署名検証により不正な呼び出しをブロック
- Twilio API を使って SMS を送信
- 処理結果を console.log()などで CloudWatch Logs に記録
事前準備
環境変数として以下の値を Lambda に設定してください:
| 環境変数名 | 内容 | 
|---|---|
| TWILIO_ACCOUNT_SID | Twilio のアカウント SID | 
| TWILIO_AUTH_TOKEN | Twilio の認証トークン(機密情報) | 
| TWILIO_PHONE_NUMBER | 送信元電話番号(+81〜の形式) | 
| SLACK_SIGNING_SECRET | Slack アプリの Signing Secret | 
| SAFE_MODE | trueの場合、 Twilio API 呼び出しを抑制 | 
 実装例:index.js
const twilio = require("twilio");
const crypto = require("crypto");
const querystring = require("querystring");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const SAFE_MODE = process.env.SAFE_MODE === "true";
exports.handler = async (event) => {
  const headers = Object.fromEntries(
    Object.entries(event.headers || {}).map(([k, v]) => [k.toLowerCase(), v])
  );
  const timestamp = headers["x-slack-request-timestamp"];
  const signature = headers["x-slack-signature"];
  if (!timestamp || !signature) {
    console.log("❌ 必要な署名ヘッダーがありません");
    return;
  }
  const now = Math.floor(Date.now() / 1000);
  // リプレイ攻撃対策
  if (Math.abs(now - timestamp) > 60 * 5) {
    console.log("❌ リクエストが古すぎます");
    return;
  }
  const rawBody = event.isBase64Encoded
    ? Buffer.from(event.body, "base64").toString("utf8")
    : event.body;
  const baseString = `v0:${timestamp}:${rawBody}`;
  const expectedSignature =
    "v0=" +
    crypto
      .createHmac("sha256", process.env.SLACK_SIGNING_SECRET)
      .update(baseString)
      .digest("hex");
  // 署名検証
  if (
    !crypto.timingSafeEqual(
      Buffer.from(signature, "utf8"),
      Buffer.from(expectedSignature, "utf8")
    )
  ) {
    console.log("❌ 署名検証に失敗しました");
    return;
  }
  const parsed = querystring.parse(rawBody);
  const text = parsed.text || "";
  const [to, ...messageParts] = text.trim().split(/\s+/);
  const message = messageParts.join(" ");
  if (!to || !message) {
    console.log("❌ 入力形式が正しくありません。使用例: /sms +81XXXXXXXXXX メッセージ");
    return;
  }
  if (SAFE_MODE) {
    console.log("🛡️ SAFE_MODE 有効中。Twilio API 呼び出しをスキップします。");
    console.log("SKIP to:", to);
    console.log("SKIP message:", message);
    return;
  }
  try {
    const result = await client.messages.create({
      body: message,
      from: process.env.TWILIO_PHONE_NUMBER,
      to: to,
    });
    console.log("✅ SMS 送信成功:", result.sid);
  } catch (err) {
    console.error("❌ SMS 送信失敗:", err.message);
  }
};
このコードを index.js として保存し、以下のコマンドで zip パッケージを作成してアップロードします:
npm init -y
npm install twilio
zip -r function.zip .
function.zip を Lambda コンソールにアップロードしてデプロイします。
動作確認
- Lambda コンソールで該当関数を開く
- 右上の「テスト」→「テストイベントの構成」を選択
- 以下のようなテスト JSON を貼り付けます:
{
  "headers": {
    "X-Slack-Request-Timestamp": "1234567890",
    "X-Slack-Signature": "v0=fake"
  },
  "body": "text=%2B819012345678+テストメッセージ",
  "isBase64Encoded": false
}
- 「テスト」ボタンをクリック
- 成功すれば ✅ SMS 送信成功または🛡️ SAFE_MODE 有効中といったログが表示されます
- より詳細なログは CloudWatch Logs で確認可能です
Lambda(proxy) 実装
Slack スラッシュコマンド /sms から送られたリクエストは、まずこの proxy 関数で受け取ります。この関数は以下のような役割を担います:
- Slack からのリクエスト(署名ヘッダー付き HTTP POST)を受け付ける
- 署名検証や Twilio 呼び出しは行わず、そのまま本体 Lambda に非同期で転送
- Slack 側には即座に 200 OKを返すことで、3 秒タイムアウト制限を回避
環境変数の設定
この proxy 関数には、以下の環境変数を設定します:
| 環境変数名 | 内容 | 
|---|---|
| SMS_HANDLER_FUNCTION_NAME | 呼び出し先 Lambda の関数名またはフル ARN | 
IAM ロール設定(lambda → lambda の実行)
Lambda(proxy) が Lambda(本体) を呼び出すには、呼び出し元の IAM ロールに適切な権限を付与する必要があります。
Lambda(proxy) に割り当てられている実行ロールに、以下のインラインポリシーまたはマネージドポリシーを追加してください。
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:sms-handler"
    }
  ]
}
 実装例:index.js
const { LambdaClient, InvokeCommand } = require("@aws-sdk/client-lambda");
const client = new LambdaClient({ region: "ap-northeast-1" });
exports.handler = async (event) => {
  const command = new InvokeCommand({
    FunctionName: process.env.SMS_HANDLER_FUNCTION_NAME,
    InvocationType: "Event", // 非同期呼び出し
    Payload: Buffer.from(JSON.stringify(event)), // リクエストそのまま転送
  });
  try {
    const response = await client.send(command);
    console.log("✅ SMS handler invoked:", response.StatusCode);
  } catch (err) {
    console.error("❌ Failed to invoke SMS handler:", err);
  }
  return {
    statusCode: 200,
    body: "リクエストを受け付けました",
  };
};
Lambda(proxy) に API Gateway トリガーを設定する
Slack のスラッシュコマンドからのリクエストを受け取るために、Lambda(proxy) に API Gateway をトリガーとして追加します。ここでは、Lambda コンソールから直接 HTTP API を作成する方法を紹介します。 トリガーの追加 ボタンをクリックします。
- トリガータイプとして- API Gatewayを選択します。
- API タイプは- HTTP APIを選択します。
- API の作成方法は- 新しい APIを選択します。
- セキュリティは- オープン(認証なし) を選択します。
トリガーが追加されると、 API Gateway のエンドポイント URL が表示されます。この URL をコピーして控えておきます。後ほど Slack のスラッシュコマンドの設定で使用します。
API Gateway の設定を開き、左メニュー Integrations > 作成した API の ANY > 統合を管理 を選択します。 ペイロード形式のバージョン を 1.0 に設定します。
Slack アプリの設定
Signing Secret の取得
署名検証に使う Signing Secret は、Slack アプリ作成後の Basic Information ページから取得できます。
- Slack API にアクセス
- Create New App→- From scratch
  
- 名前とワークスペースを指定してアプリ作成
- 左メニュー Basic Information→App CredentialsからSigning Secretをコピー
  
スラッシュコマンドの作成
Slack アプリ左メニュー Slash Commands → Create New Command を選択し、以下のように設定:

| 項目 | 値 | 
|---|---|
| Command | /sms | 
| Request URL | API Gateway のエンドポイント (例: https://abc123.execute-api.ap-northeast-1.amazonaws.com/sms) | 
| Short Description | Twilio API を使用して SMS を送信します | 
| Usage Hint | +81XXXXXXXXXX メッセージ本文 | 
アプリのインストール
左メニュー Install App → Install to Workspace をクリックし、権限を許可してインストールします。
動作確認
Slack 上で次のようにコマンドを入力します:
/sms +819012345678 Slack からのテスト送信
Lambda 関数が署名を検証し Twilio API が呼び出します。
次のような SMS が届きます。

まとめと今後の展望
本記事では、Slack スラッシュコマンドから Twilio の SMS API を呼び出すシンプルな構成として、Lambda(proxy) + Lambda(本体) の 2 段構成による非同期処理パターンを紹介しました。
SQS や Step Functions を使わず、Lambda の Event モードを活用することで構成を大幅に簡素化しつつ、Slack の 3 秒タイムアウト制限を安全に回避できる点が最大のメリットです。
今後はこの構成を応用し、以下のようなユースケースにも展開できます:
- 複数のバックエンド処理(メール送信、ファイル変換など)への共通的なリクエストハンドラ
- Slack 以外の外部サービス(Webhook、API Gateway 経由の REST リクエスト)との連携
- Event モードの性質を活かした、安全な「実行要求受付」エントリポイントの構築
本構成は、小規模検証だけでなく本番でも十分に活用可能です。ぜひ皆さんのユースケースでも試してみてください。










