
SlackコマンドでGemini APIを呼び出してみる
SHIFTのKoshiishiです。
以前の記事 でgeminiのapiをhonoでラップしました。
直接叩くのは少し面倒なので、slackで呼び出せるようにしてみました。
最終的なコード です。
構成図
全体の構成としては図の通りになります。

Slack Signing Secretの準備
GeminiのAPIキーと、SlackのSigning Secretの2つが必要です。
Signing SecretはSlackからのリクエスト検証のため用います。
GeminiのAPIキーは以前の記事 で作成済みとさせてください。
SlackのSigning Secretは次の手順で作成します。
Create New App から、Slackアプリを作成します。 画像では、もともとaws_cost_notificatorというbotが存在するワークスペースに、hono-gemini-botというアプリを作成しています。




Signing Secretが生成されるので、こちらを控えておきます。
コードの準備
Slack Signing Secretでのバリデーション
前回の記事で使用したコードをパブリックテンプレート にしたので、右上のUse this tempateからコードを作成します。
wrangler.tomlや、package.jsonでhono-geminiとなっている箇所を、hono-gemini-slackに書き換えます。(名前衝突しないよう)
環境変数のinterfaceからAPI_KEYを廃止して、SLACK_SIGNING_SECRETを追加します。
interface Env {
GEMINI_API_KEY: string
SLACK_SIGNING_SECRET: string
}
API_KEYでのバリデーションを、SLACK_SIGNING_SECRETでのバリデーションに差し替えます。
validateSlackSignature.tsを作成し、ヘッダーから署名とタイムスタンプを取得するようにします。
署名が一致しない場合や、リクエストの送信日時が古すぎる場合などは弾くようにします。
コード全体はこちら
export const validateSlackSignature = (getSigningSecret: (c: Context) => string): MiddlewareHandler => {
return async (c, next) => {
const timestamp = c.req.header('X-Slack-Request-Timestamp')
const signature = c.req.header('X-Slack-Signature')
if (!timestamp || !signature) {
return c.json({ error: 'Missing Slack signature headers' }, 401)
}
...
Slackのタイムアウトを予防
レスポンスによってはタイムアウトします。
スラッシュコマンドからの実行は公式ドキュメント 曰く3秒らしく下記のコードは、タイムアウトしたり、しなかったりします。
app.post('/slack', async (c) => {
try {
const parsedBody = c.get('parsedBody') as Record<string, string>
const question = parsedBody.text || 'No question provided'
const genAI = new GoogleGenerativeAI(c.env.GEMINI_API_KEY)
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp" })
const result = await model.generateContent(question)
const response = result.response
return c.json({
response_type: "in_channel",
text: response.text()
})
} catch (error) {
return c.json({ error: 'error occurred' }, 500)
}
})
公式の推奨通りの実装に変更します。
一旦すぐに200を返し、その時のresponse_urlを控えておき、そこにPOSTするようにします。
const parsedBody = c.get('parsedBody') as Record<string, string>
const question = parsedBody.text || 'No question provided'
const response_url = parsedBody.response_url
// Immediately return a processing response
c.executionCtx.waitUntil((async () => {
// Asynchronously call the model
const genAI = new GoogleGenerativeAI(c.env.GEMINI_API_KEY)
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp" })
const result = await model.generateContent(question)
const finalText = `${question}\n${result.response.text()}`
// After processing, POST to Slack's response_url to update the message
await fetch(response_url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
response_type: "in_channel",
text: finalText
})
})
})())
return c.json({
response_type: "ephemeral",
text: `Your question: "${question}" is being processed. Please wait for the response.`
})
CloudflareWorkerの準備
控えておいたSLACK_SIGNING_SECRETと、前回作成したGEMINI_API_KEYをCloudflareに登録します。
❯ wrangler deploy
❯ wrangler secret put GEMINI_API_KEY
❯ wrangler secret put SLACK_SIGNING_SECRET
Slackアプリ側の準備
アプリ設定のSlach Commandsから、新規のコマンドを登録します。
Request URLには、wrangler deploy後のエンドポイント/slakを書きます。

動作確認
(ログをみたい場合)
❯ wrangler tail
して、logを見れる状態にしておきます。
この状態にしておくことで、Slackからリクエストが来た際のステータスが見れます。
Slackから質問を投げると画像のようになります。
左側がSlack, 右側がターミナルです。
全体の流れとしては以下の通りです。
ephemeralなテキスト(Onli visible to you)で自分にだけ質問が見えます
tailコマンドで見ているターミナルでどこにPOSTが走ったか見えます
Slackに通常テキストとして、geminiからの返答が返されます

(Slackはmrkdwn 形式のためかmarkdown崩れてしまっていますが、今回の記事では放置しています)
おわりに
ここまで、GeminiのAPIをCloudflare Workers上のHonoフレームワークでラップし、さらにSlackとの連携を組み合わせる方法を解説してきました。
これにより、わざわざエンドポイントを直接叩かなくても、Slackのスラッシュコマンドから便利にLLMを呼び出して応答を得ることができます。
今回の方法が、皆さんのワークフロー改善に少しでも役立てば幸いです。
執筆者プロフィール:Kenta Koshiishi
スタートアップ企業でFigmaを使用したUI設計から、Rustでのバックエンド開発、Reactでのフロントエンド開発まで、一気通貫で手掛けてきた。 SHIFTではシニアエンジニアとして入社し、既存のWebアプリに触れ始めた。
✅この筆者のおすすめ記事
✅SHIFTへのご相談、お問合せはお気軽に
SHIFTのサービスについて(サービスサイト)
SHIFTの導入事例
お役立ち資料はこちら
SHIFTの採用情報はこちら