[Zendesk+Slack] Slack スラッシュコマンドから Zendesk チケットを起票する仕組みを構築する [2/3]

[Zendesk+Slack] Slack スラッシュコマンドから Zendesk チケットを起票する仕組みを構築する [2/3]

Slack のスラッシュコマンドから Zendesk にチケットを起票する仕組みを構築します。AWS Lambda + SQS を用いた非同期構成を採用しました。第 2 回では、SQS キューの作成と 2 つの Lambda 関数の実装を通じて、リクエストの受付から Zendesk API 呼び出しまでの基盤を構築します。

対象読者

  • Slack から問い合わせを起票する仕組みを自前で構築したい方
  • AWS Lambda や Amazon SQS を使って非同期処理を実装したい方
  • Zendesk の API を活用したカスタム連携を検討している方

参考記事


検証環境

バージョン

node: 22.13.1  
npm: 10.9.2  

概要

本記事では、Slack のスラッシュコマンドを使って、AWS 経由で Zendesk にチケットを自動起票する仕組みのうち、バックエンド構成の実装方法を紹介します。

第 2 回では、Slack からのリクエストを受け取り、Zendesk API を非同期で呼び出すための基盤として、以下の 3 コンポーネントを実装します。

  • Amazon SQS:非同期処理のためのメッセージキュー
  • Lambda 関数(前段):Slack からのリクエストを受信し、SQS に送信
  • Lambda 関数(後段):SQS をトリガーに Zendesk API を呼び出してチケットを作成

構成図のうち、赤枠部分の実装を行います。

構成図


SQS キューの作成

Amazon SQS コンソール にアクセスし、「キューを作成」をクリックします。

項目 設定値
キュータイプ 標準
キュー名 slack-zendesk-queue(任意)

その他の設定はデフォルトのままで構いません。


SQS キュー URL の取得

作成したキューの詳細画面を開き、「キューの URL」をコピーして控えておきます。
後述の Lambda 関数(前段)からメッセージを送信する際に使用します。


SQS → Zendesk 起票用 Lambda 関数(後段)の作成

Slack からのリクエストはすでに SQS に格納されているため、ここでは SQS をトリガーにしてメッセージを受け取り、Zendesk API を呼び出してチケットを作成する Lambda 関数を作成します。


1. Lambda 関数の作成

AWS Lambda コンソール にアクセスし、「関数を作成」を選択します。

項目
関数名 slack-zendesk-ticket-sender(任意)
ランタイム Node.js 22.x
実行ロール 任意(後述でポリシー付与)

2. コードの入力(index.js

関数を作成後、「コード」タブに表示されるエディタで、 index.js を作成し、編集します。

この関数では、SQS から受け取った subject(件名)と message(本文)を使って、Zendesk API を呼び出し、1 件のチケットを作成します。

Zendesk API の認証は メールアドレス + API トークンによる Basic 認証で行っており、POST リクエストで /api/v2/tickets.json にチケットデータを送信しています。

コード全体を Lambda コンソールの「コード」タブにあるエディタに貼り付けてください。

const https = require("https");

const ZENDESK_DOMAIN = process.env.ZENDESK_DOMAIN;
const ZENDESK_EMAIL = process.env.ZENDESK_EMAIL;
const ZENDESK_API_TOKEN = process.env.ZENDESK_API_TOKEN;
const SAFE_MODE = process.env.SAFE_MODE === "true";

exports.handler = async (event) => {
  for (const record of event.Records) {
    const body = JSON.parse(record.body);

    const subject = body.subject || "Slack からの問い合わせ";
    const message = body.message || "";

    if (SAFE_MODE) {
      console.log("🛡️ SAFE_MODE 有効化中のため、Zendesk API 呼び出しをスキップします。");
      console.log("SKIP subject:", subject);
      console.log("SKIP message:", message);
      continue;
    }

    const postData = JSON.stringify({
      ticket: {
        subject,
        comment: { body: message }
      }
    });

    const auth = Buffer.from(`${ZENDESK_EMAIL}:${ZENDESK_API_TOKEN}`).toString("base64");

    const options = {
      hostname: ZENDESK_DOMAIN,
      path: "/api/v2/tickets.json",
      method: "POST",
      headers: {
        "Authorization": `Basic ${auth}`,
        "Content-Type": "application/json",
        "Content-Length": Buffer.byteLength(postData)
      }
    };

    await new Promise((resolve, reject) => {
      const req = https.request(options, (res) => {
        let data = "";
        res.on("data", chunk => data += chunk);
        res.on("end", () => {
          if (res.statusCode === 201) {
            console.log("✅ チケット作成成功");
            resolve();
          } else {
            console.error("❌ Zendesk エラー:", res.statusCode, data);
            reject(new Error(`Zendesk returned ${res.statusCode}`));
          }
        });
      });

      req.on("error", (e) => {
        console.error("❌ リクエストエラー:", e);
        reject(e);
      });

      req.write(postData);
      req.end();
    });
  }
};

3. 環境変数の設定

関数の「設定」タブ →「環境変数」セクションで以下を追加します。

キー 値の例
ZENDESK_DOMAIN yourcompany.zendesk.com
ZENDESK_EMAIL you@example.com/token
ZENDESK_API_TOKEN xxxxxxxxxxxxxxxxxxxxx
SAFE_MODE true (テスト中や保守中など)

4. IAM ロールのポリシー追加

この関数は SQS から呼び出されるため、以下のポリシーをロールに追加します。

アタッチするマネージドポリシー

  • AWSLambdaSQSQueueExecutionRole

SQS からのアクセスを許可する

設定 タブの アクセス権限 から リソースベースのポリシーステートメント に以下のアクセス権限を追加します。

項目 設定値
ステートメント ID AllowSQSInvoke
プリンシパル sqs.amazonaws.com
アクション lambda:InvokeFunction

34


5. SQS とのトリガー連携

SQS コンソールに移動し、対象キューを開きます。

  • 「Lambda トリガー」タブを選択
  • 「Lambda 関数を追加」→ slack-zendesk-ticket-sender を選択
  • 追加後、メッセージが入るたびに関数が自動実行されます

6. 動作確認

Lambda コンソールの「テスト」から以下のようなイベントを入力してください:

{
  "Records": [
    {
      "body": "{\"subject\": \"テスト件名\", \"message\": \"これはテストメッセージです。\"}"
    }
  ]
}

CloudWatch Logs 上で ✅ チケット作成成功 または 🛡️ SAFE_MODE 有効化中のためスキップ が確認できれば成功です。

Slack リクエストを受け取り SQS に送信する Lambda 関数(前段)の作成

Slack のスラッシュコマンドから送信されるリクエストを受け取り、SQS に問い合わせ内容を送信する Lambda 関数を実装します。Slack リクエストは署名ヘッダーによって検証可能なため、X-Slack-Signature / X-Slack-Request-Timestamp を使った認証処理を含めます。


1. Lambda 関数の作成

AWS Lambda コンソール にアクセスし、「関数を作成」を選択します。

項目
関数名 slack-zendesk-ticket-request-handler(任意)
ランタイム Node.js 22.x
実行ロール 任意(後述でポリシー付与)

2. ローカルでプロジェクトディレクトリを作成

mkdir slack-zendesk-request-handler
cd slack-zendesk-request-handler
npm init -y
npm install aws-sdk

3. index.js を作成

この関数は、Slack のスラッシュコマンドから送られてくる HTTP リクエストを受け取り、署名の正当性を検証したうえで、その内容を Amazon SQS に送信する処理を行います。

Slack からのリクエストは application/x-www-form-urlencoded 形式で送られてくるため、user_nametext などの値をパースし、問い合わせ内容として整形して送信します。
また、署名検証によって 外部からの不正なリクエストを防ぐセキュリティ処理も組み込んでいます。

const crypto = require("crypto");
const querystring = require("querystring");
const { SQS } = require("aws-sdk");

const sqs = new SQS();
const QUEUE_URL = process.env.QUEUE_URL;
const SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET;

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) {
    return { statusCode: 400, body: "Missing headers" };
  }

  // Replay attack 対策
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > 60 * 5) {
    return { statusCode: 403, body: "Request too old" };
  }

  const rawBody = event.isBase64Encoded
    ? Buffer.from(event.body, "base64").toString("utf8")
    : event.body;

  const sigBaseString = `v0:${timestamp}:${rawBody}`;
  const expectedSig = "v0=" + crypto
    .createHmac("sha256", SIGNING_SECRET)
    .update(sigBaseString, "utf8")
    .digest("hex");

  // 署名検証
  if (
    !crypto.timingSafeEqual(
      Buffer.from(signature, "utf8"),
      Buffer.from(expectedSig, "utf8")
    )
  ) {
    return { statusCode: 403, body: "Invalid signature" };
  }

  const body = querystring.parse(rawBody);
  const user = body.user_name || "unknown";
  const text = body.text || "";

  const message = {
    subject: `Slack ユーザー ${user} からの問い合わせ`,
    message: text
  };

  await sqs.sendMessage({
    QueueUrl: QUEUE_URL,
    MessageBody: JSON.stringify(message)
  }).promise();

  return {
    statusCode: 200,
    body: "受け付けました"
  };
};

4. 環境変数の設定

Lambda に以下の環境変数を追加します。

キー
QUEUE_URL Slack リクエストを送信する SQS の URL
SLACK_SIGNING_SECRET Slack アプリの Signing Secret(後で取得)

5. zip 化とアップロード

zip -r function.zip .

Lambda コンソールで関数を作成後、「.zip ファイルをアップロード」から function.zip を選択してください。


6. IAM 権限の設定

実行ロールに以下のインラインポリシーを付与します。 Resource には作成したキューの ARN を入力してください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sqs:SendMessage",
      "Resource": "arn:aws:sqs:********"
    }
  ]
}

7. API Gateway のトリガーを追加

Lambda 関数に HTTP API トリガーを追加します。署名検証が入っているため、Slack 側のスラッシュコマンド設定にこのエンドポイント URL を指定すれば安全に通信できます。

API Gateway の設定を開き、左メニュー Integrations > 作成した API の ANY > 統合を管理 を選択します。 ペイロード形式のバージョン を 1.0 に設定します。

作成したエンドポイント URL は、 第 3 回の Slack App の設定編で使用するので、控えておきます。

8. 動作確認

開発中に署名検証のテストが難しい場合は、署名チェック部分を一時的にスキップすることも可能ですが、CloudWatch Logs 上で署名の計算式・比較ログを出力するなど、安全かつ確認可能な形で検証することを推奨します。
本番運用では必ず署名チェックを有効化した状態で運用してください。

Lambda コンソールの「テスト」タブから以下のようなイベントを送信します:

{
  "headers": {
    "X-Slack-Request-Timestamp": "1234567890",
    "X-Slack-Signature": "v0=fake"
  },
  "body": "user_name=testuser&text=問い合わせ内容です"
}

まとめ

第 2 回では、Slack コマンド受付から Zendesk チケット作成までの非同期処理フローを構築しました。

  • Lambda(前段)で署名検証を行い、SQS に問い合わせ内容を送信
  • Lambda(後段)で SQS メッセージを受け取り、Zendesk API を通じてチケットを作成
  • SAFE_MODE により、検証中の誤課金や冗長起票を回避可能な設計を採用

次回は、Slack アプリの作成とスラッシュコマンドの設定、実際の動作確認を行い、全体構成を完成させます。


Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.