Slack App でハローワールド - with Cloudflare Workers

Slack App でハローワールド - with Cloudflare Workers

最小限のサンプル的な Slack App を作ってみます。

つくる Slack アプリ:チャンネルからボタンをポチッと押すと「Hello world!」と挨拶してくれるボット

サーバは Cloudflare Workers です。Slack App の作り方で調べると GAS(Google Apps Script)で作成しているものが多いですが、スプレッドシートとの連携が必要なく、単純なサーバスクリプトを作るだけであれば Cloudflare Workers がおすすめです。GAS 同様に無料で使えるだけでなく、GAS と比較するとレートリミットに余裕があります。(また、少し細かい情報になりますが、GAS はリクエストヘッダーのデータを参照できないという仕様上、Slack App からのリクエストの正当性検証を妥協しなければならない部分が出てきます。)

以下、ハンズオン形式で記載します。

1. Slack アプリの作成と設定 #

1-1. Slack アプリを作成 #

まずは Slack アプリを作りましょう。

https://api.slack.com/apps にアクセス。

Create an App を押下します。

screenshot

From scratch を選択します。

screenshot

App Name を入力。Pick a workspace to develop your app in: でアプリを追加したいワークスペースを選択しましょう。

screenshot

1-2. Slack アプリへのインタラクションを有効化する #

作成した Slack アプリの設定を変えていきます。まずは、

つくる Slack アプリ:チャンネルからボタンをポチッと押すと「Hello world!」と挨拶してくれるボット

の「チャンネルからボタンをポチッと押すと」のイベントをボットが検知できるように設定を変えます。

サイドメニューから Interactivity & Shortcuts に移動します。

screenshot

Interactivity を On にします。

screenshot

Request URL に https://example.com を入力します。この値は仮置きです。後続の作業で正式なものに差し替えます。(CLoudflare Workers でサーバ処理を書いたあとで)

screenshot

下にスクロールして Shortcuts の Create New Shortcut を押下します。

screenshot

Global を選択した状態で Next を押下。

screenshot

Details の内容を入力したら Create を押下します。

screenshot

Save Changes を押下して保存しましょう。

screenshot

1-3. Slack アプリの権限を設定する #

つくる Slack アプリ:チャンネルからボタンをポチッと押すと「Hello world!」と挨拶してくれるボット

チャンネルでボタンが押されたことを検知するための権限と、「こんにちは」と書き込むための権限をボットに付与する必要があります。

サイドメニューから OAuth & Permissions に移動します。

screenshot

下にスクロールして Scopes の Bot Token Scopes にある Add an OAuth Scope から次の2つの権限をつけましょう。

  • commands:Slack App を起動するために必要な権限
  • chat:write:Slack App がチャンネルに書き込むために必要な権限

screenshot

1-4. Slack アプリの表示名を設定する #

サイドメニューから App Home に移動します。

screenshot

Your App’s Presence in Slack の App Display Name の Edit を押下します。

内容を入力します。

screenshot

1-5. Slack アプリの表示名を設定する #

ここまで設定してきた Slack アプリをワークスペースにインストールしましょう。

サイドメニューから OAuth & Permissions に移動します。

screenshot

Allow を押下します。

screenshot

1-6. 必要なシークレット情報をメモしておく #

サイドメニューから Basic Information に移動し、Signing Secret の内容をメモしておきましょう。

screenshot

サイドメニューから OAuth & Permissions に移動し、Bot User OAuth Token の内容をメモしておきましょう。

screenshot

Bot を動かしたいチャンネルの ID をメモしておきましょう。Slack アプリのページから離れて、Slack のアプリケーション本体を開きます。

Copy link でチャンネルのリンクをコピーして適当なところにペーストします。

https://my-workspace.slack.com/archives/C0*********

貼付けされた内容の C0********* の部分がチャンネル ID です。

screenshot

2. Cloudflare Workers で処理を実装 #

Cloudflare のダッシュボードにログインします。https://dash.cloudflare.com/

2-1. Cloud flare Workers アプリを作成 #

Workers & Pages に移動し、Create application を押下します。

screenshot

Start with Hello World! を選択。

screenshot

Deploy を押下。

screenshot

2-2. コードを編集 #

右上の Edit code を押下。

screenshot

以下のコードをコピペして貼り付けたら右上の Deploy を押下。

screenshot

コード

AI にサクッとつくってもらいました。

/**
 * Welcome to Cloudflare Workers! This is your first worker.
 *
 * - Run "npm run dev" in your terminal to start a development server
 * - Open a browser tab at http://localhost:8787/ to see your worker in action
 * - Run "npm run deploy" to publish your worker
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */

export default {
  async fetch(request, env, ctx) {
    return await main(request, env, ctx);
  },
};

async function main(request, env, ctx) {
  const { SLACK_CHANNEL_ID, SLACK_SIGNING_SECRET, SLACK_BOT_TOKEN } = env;

  if (request.method !== "POST") {
    return new Response("Method Not Allowed", { status: 405 });
  }

  const ct = request.headers.get("content-type") || "";
  if (!ct.includes("application/x-www-form-urlencoded")) {
    return new Response("Content Type Not Allowed", { status: 415 });
  }

  const rawBody = await request.clone().text();
  if (!(await verifySlackSignature(request, rawBody, SLACK_SIGNING_SECRET))) {
    return new Response("Unauthorized", { status: 401 });
  }

  // 3秒以内に Slack へレスポンスを返さないと Slack がタイムアウト判定してしまうので、投稿処理は非同期で実行してレスポンスを先に返す
  ctx.waitUntil(postMessage(SLACK_BOT_TOKEN, SLACK_CHANNEL_ID, "Hello world!"));
  return new Response("", { status: 200 });
}

async function postMessage(token, channel, text) {
  await fetch("https://slack.com/api/chat.postMessage", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json; charset=utf-8",
    },
    body: JSON.stringify({ channel, text }),
  });
}

// Slack Signing Secret 署名検証
async function verifySlackSignature(request, rawBody, signingSecret) {
  const ts = request.headers.get("x-slack-request-timestamp");
  const sig = request.headers.get("x-slack-signature");
  if (!ts || !sig || !signingSecret) return false;

  // リプレイ対策(5分)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(ts)) > 60 * 5) return false;

  const base = `v0:${ts}:${rawBody}`;
  const expected = `v0=${await hmacSha256Hex(signingSecret, base)}`;
  return timingSafeEqual(expected, sig);
}

async function hmacSha256Hex(key, message) {
  const enc = new TextEncoder();
  const cryptoKey = await crypto.subtle.importKey(
    "raw",
    enc.encode(key),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  const mac = await crypto.subtle.sign("HMAC", cryptoKey, enc.encode(message));
  return [...new Uint8Array(mac)]
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}

function timingSafeEqual(a, b) {
  const enc = new TextEncoder();
  const ba = enc.encode(a);
  const bb = enc.encode(b);
  if (ba.length !== bb.length) return false;
  let diff = 0;
  for (let i = 0; i < ba.length; i++) diff |= ba[i] ^ bb[i];
  return diff === 0;
}

2-3. シークレットを環境変数に設定 #

上部のタブから Settings を押下します。

screenshot

Variables and Secrets の Add を押下。

screenshot

メモしておいた3つのシークレットを環境変数に設定しましょう。

  • SLACK_CHANNEL_ID(例:C**********
  • SLACK_SIGNING_SECRET(例:********************************
  • SLACK_BOT_TOKEN(例:xoxb-************-**************-************************

screenshot

screenshot

3. Slack アプリのインタラクションの URL 設定を更新する #

仮置きで https://example.com としていた値を更新します。

Cloudflare Workers のアプリのページ右上の Visit を押下します。

screenshot

開いたページの URL をコピーします。

screenshot

Slack アプリの設定ページのサイドメニューから Interactivity & Shortcuts に移動します。

Cloudflare Workers の URL を貼り付けてから Save Changes を押下して更新しましょう。

screenshot

3. Slack チャンネルに Slack App を追加する #

Slack 本体のアプリケーションを開き、Slack App を追加したいチャンネルの設定の Integrations タブから Add an App を選択。

screenshot

Add で今回作成した Slack App を追加します。

screenshot

4. 動作確認 #

Slack App を追加したチャンネル上で Run shortcut を選択しましょう。(あるいはメッセージ欄に/ / を入力しても同じ)

screenshot

自分の追加したアプリのコマンドが表示されます。押下。

screenshot

Hello world! と挨拶されたら成功です!

screenshot

99. デバッグしたいときは #

もし Cloudflare workers のコードをデバッグしたいときは、ローカルでターミナルを開き、以下を実行します。

npx wrangler tail <あなたの Cloudflare Worker アプリの ID>

例えば今回示してきた Cloudflare Worker アプリの ID は divine-moon-9e37 でしたので以下です。そうすると Cloudflare Worker で実行された処理のログがリアルタイムで出力されるようになります。console.log() の結果などが出力されます。

npx wrangler tail divine-moon-9e37

 ⛅️ wrangler
───────────────────
Successfully created tail, expires at 2026-01-01T13:30:00Z
Connected to divine-moon-9e37, waiting for logs...

この状態で Slack App を呼び出し、Cloudflare Worker で何かエラーが起きれば上記にログが出力されますのでこれでデバッグしましょう。