hacomono TECH BLOG

フィットネスクラブやスクールなどの顧客管理・予約・決済を行う、業界特化型SaaS「hacomono」を提供する会社のテックブログです!

Slack のCustom functionで外部API連携をやってみた

この記事は hacomono advent calendar 2024 の23日目の記事です

こんにちは、hacomonoの開発基盤組織でインターンをしているドラゴンです。
hacomonoのインターンでは、社内のエンジニアや他業種の方の業務効率化のため社内ツールを開発しております。その中で、Python for Slack SDKを活用し、Slack ワークフローや Custom functionによる外部API連携の実装に挑戦する機会がありました。その際にCustom functionのドキュメントが少なく、自分では理解に手間取ってしまいました。
そのため、設定からCustom function が動くまでの実装手順を整理したいと思い執筆いたしました。

この記事について

想定読者

  • これから、Custom functionを使ってみたい方
  • Slackワークフローの基本知識がある方

本記事のゴール

記事の内容を通して、読者が以下の2点について具体的なイメージを持ってもらえるようになることを目指します。

  1. Custom functionの基本的な設定手順
  2. ワークフローからのパラメータ受け取りの実装

触れること

  • Custom functionの基本的な設定手順の説明
  • ワークフローからパラメータを受け取る方法の解説
  • 外部APIとの連携例の紹介

触れないこと

  • Slack APIの基本的な解説
  • Python/Boltの環境構築
  • トークンの取得方法の詳細


1. はじめに

Custom functionとは

Custom functionとは、Slackワークフローにカスタムステップを追加できる機能です。Bolt SDKを使用して独自の処理を実装でき、外部APIとの連携や複雑なデータ処理など、標準のワークフローステップでは実現できない機能を追加することができます。

課題

私がインターンでCustom functionを作成する中で、設定から実装までの具体的な手順をまとめた資料が少なく感じ、以下のような課題に直面することが多いです。

  • 設定手順が分かりにくい
  • パラメータの受け取り方が不明確

今回の記事では、これらの課題を解決するため、実際のユースケースを例に説明していきます。

実装例:社内インシデント管理システムとの連携

上記の課題をわかりやすく示すために、本記事では社内インシデント管理システムとの連携を例に挙げて解説します。
一般的に、組織内での情報共有はSlack上で行われることが多く、インシデントは専用チャンネルで報告されるケースがあります。しかし、外部のインシデント管理システムへ手動で登録するのは手間がかかります。そこで、Custom functionを活用すれば、Slack上のやりとりをトリガーとして外部APIにデータを自動送信できます。

※これは実装例のため、社内で運用されてるツールでもなく、インターン生が取り組んだタスクでもございません。



完成イメージ

  • インシデント発生時、担当者はチャンネル内に固定してあるリンクからワークフローを起動
  • フォームに基本情報(担当者・優先度・緊急度)を入力
  • Custom function経由で外部システムに登録 → Slack上のチャンネルにも「インシデントが登録されました」とメッセージをポスト



2. 環境整理

開発するのに必要な環境

Custom functionを使用するには、以下の要件を満たす必要があります。

  1. Slackワークスペースの要件
    • 有料プランに加入していること
    • 管理者権限を持っていること
  2. 開発環境の要件


事前準備

  1. アプリケーションの作成
    • Slack APIのページでアプリを作成
    • 基本的な設定の実施
  2. 必要なトークンの取得
    • Bot User OAuth Token
    • App Level Token
    • SLACK SIGNING SECRET

Slack APIのページはこちら



3. 基本設定

ワークフローの基本設定

まず、「その他」→ 「自動化」 → 「新しいワークフロー」を選択します。



ワークフローを開始するタイミングを、任意のタイミングで始めたいため「Slack内のリンクから開始」に設定します。
インシデント報告フォームを作成します。以下のような項目を含めます。

  • 優先度
  • 緊急度合い
  • 担当者情報



評価フォーム

組織レベルのアプリ設定

アプリをワークスペース全体で利用できるようにする必要があります。これはCustom functionを使用するための最初のステップです。

  1. Slackアプリの設定ページにアクセスします。
  2. 左サイドバーから「Org Level Apps」を選択します。
  3. Organization Level Apps」を有効化しまs。
  4. 要件にあった必要なスコープを承認します。

リクエストURLの設定

Custom functionを使用するために、以下のどちらかの設定が必要です。

1.Event Subscriptionsの場合

外部にホストしたサーバーのURLを指定

  • HTTPSが必須
  • Slackからのリクエストを受け取れる必要がある

メリット

  • セキュアなHTTPS通信ができる
  • 本番環境での運用に適している

デメリット

  • セットアップに時間とコストがかかる
  • 3秒以内にSlackにレスポンスしないといけない制約がある


2.Socket Mode(今回の選択)

外部に公開されたURLが不要で、ローカル開発時でも使用できる

メリット

  • 外部サーバー不要で手軽に開始
  • リアルタイムな通信が可能

デメリット

  • 大規模な運用には向かない
  • Slackインフラへの依存度が高い


この記事では、より手軽に試せるSocket Modeを使用して実装を進めていきます。



4. Callback IDの設定

Custom functionをSlackワークフローと紐付けるために、まず一意の識別子(Callback ID)を設定します。このCallback IDはこの後説明するパラメータ取得にも必要になります。

Callback IDの設定方法

  1. Slackアプリの設定ページにアクセス。
  2. 左タブの「Workflow Steps 【NEW】」を押下します。
  3. Add Stepを押下し以下の画像の設定画面が開きます。
  4. Basic Informationを押下し、Callback IDを取得します。



5. Custom function のパラメータ設定

Custom functionはSlack ワークフローの中で扱う変数を取得・出力するために、InputとOutput Parameterの設定が必要です。
Custom function でSlack ワークフローのフォームから値を受け取るための設定方法について解説します。

パラメーターの設定方法

  1. Slackアプリの設定ページにアクセスします。
  2. 左タブの「Workflow Steps 【NEW】」を押下します。
  3. Add Stepを押下し以下のParameterの設定画面に遷移します。



Custom functionのInput / Output Parametersを設定する際に、ワークフロー設定画面でParameter IDが自動割り当てされます。

Input Parameters(入力パラメータ)の設定

Custom functionは入力と出力を宣言する必要があります。
各パラメータには以下の項目を設定します。

  1. Input type(入力タイプ)
    • フォームの各項目に応じて適切なタイプを選択
    • 例:User(ユーザー選択用)、Integer(数値用)など
    • 必要に応じて「Make required」にチェック
  2. Parameter ID(パラメータID) このParameter IDはCustom functionでフォームの値を取得するために必要です。これらのParameter IDは、Slackワークフロー機能のWorkflow Steps設定画面でCustom functionのInput / Output Parametersを定義した際に自動生成されます。
    • 例:acc4cc(User選択用)
    • 例:7412ec(Integer入力用)

取得した値は後述するOutput Parametersで返すことができます。

Output Parameters(出力パラメータ)の設定

Output Parametersは、Custom function実行後の処理結果を後続のワークフローステップで利用するために設定します。
Input Parametersで取得したUser型の値を、Output Parametersでレスポンスすることもできます(InputとOutputのParameter IDは同一のものではない)。

また、Input Parametersで取得した情報を外部APIへ送信し、そのレスポンスをOutput Parametersとして返すことで、以降のステップでその値を活用できます。
アプリケーションの要件に応じて、必要なデータを適切な型でOutput Parametersとして定義してください。

パラメータの受け取り方

この関数は外部インシデント管理システムへの外部リクエストを行う想定です。

コード

def register_workflow_steps(app: App):
    @app.function("a2b29e") # SlackのCustom function設定で定義したcallback_id
    def handle_incident_report(step, complete, fail):
        # Input Paramatersの取得
        inputs = step.inputs
    
        # Paramater IDを使って各値にアクセス
        slack_user_id = inputs["acc4cc"]    # ユーザー情報
        priority = inputs["7412ec"]    # 優先度(数値)
    
        try:
            response = send_to_incident_system({
                "reporter": slack_user_id,
                "priority": priority,
                ...
            })
    
            complete(outputs={
                "result": "success",
                "incident_id": response["id"]
            })
        except Exception as e:
            fail(error=f"エラーが発生しました: {str(e)}")

注意点

  • パラメータのタイプと実際の使用方法を一致させましょう。
  • 必須パラメータには必ず「Make required」にチェックを入れましょう。
  • Parameter ID は他のパラメータと重複しないようにしましょう。


6. アプリケーションの実行

コマンド実行

python3 app.py

上記コマンドでアプリが起動します。Socket Modeを使用しているのでngrok のようなローカルPC上で開発サーバーをインターネットに繋げる必要はないです。

ワークフロー起動しフォームに入力

結果

コード

def register_workflow_steps(app: App):
    @app.function("a2b29e")  # SlackのCustom function設定で定義したcallback_id
    def handle_incident_report(event, inputs: dict, client, complete, fail, logger: logging.Logger):
        try:
            # Input Parametersから値を取得
            person_in_charge = inputs.get("acc4cc")   # 対応者IDのparam_id
            priority = inputs.get("7412ec")          # 優先度のparam_id
            emergency = inputs.get("a194e7")         # 緊急度のparam_id
            target_channel_id = inputs.get("5fc3ff") # メッセージを送信するチャンネルID
            # 入力値の検証
            if not all([person_in_charge, priority, emergency, target_channel_id]):
                raise ValueError("必要な入力値が不足しています")
            # インシデント管理システムへ送信するデータ作成
            incident_data = {
                "person_in_charge": person_in_charge,
                "priority": priority,
                "emergency": emergency
            }
            # 外部インシデントAPIのエンドポイント
            incident_api_url = "https://example.com/api/incidents"  # 実際のURLを設定する
            headers = {
                "Content-Type": "application/json",
                "Authorization": "Bearer YOUR_API_KEY_HERE"  # 必要に応じてAPIキーやトークンを設定
            }
            try:
                # インシデント管理システムにPOSTリクエストを送る
                response = requests.post(
                    incident_api_url,
                    json=incident_data,
                    headers=headers,
                    timeout=5
                )
                response.raise_for_status() 
            except requests.exceptions.RequestException as e:
                logger.error(f"インシデントAPI送信エラー: {e}")
                raise RuntimeError("インシデント登録に失敗しました")
            # Slackに返すメッセージテキストを組み立てる
            message_text = (
                f"【インシデントフォームの値を自社インシデントAPIに送信】\n"
                f"対応者: <@{person_in_charge}>\n"
                f"優先度: {priority}\n"
                f"緊急度: {emergency}\n"
            )
            # 指定されたチャンネルにメッセージを送信
            try:
                client.chat_postMessage(
                    channel=target_channel_id,
                    text=message_text
                )
            except Exception as e:
                logger.error(f"メッセージ送信エラー: {e}")
                raise e
            # 出力を設定(本例では入力パラメータをそのまま出力として返す)
            outputs = {
                "acc4cc": person_in_charge,
                "7412ec": priority,
                "a194e7": emergency,
            }
            complete(outputs=outputs)
        except Exception as e:
            logger.exception(e)
            fail(error=f"ワークフロー実行中にエラーが発生: {str(e)}"


感想

今回、Custom functionを用いてSlackワークフローで取得した値を、外部のインシデント管理システムに送信する実装をやってみました。
実装を進める中で特に印象的だったのは、Slackプラットフォームの拡張性の高さです。Custom functionを活用することで、かなり柔軟な機能実装が可能だと実感しました。その一方で苦戦したところもあり、パラメータの設定や取得周りは公式ドキュメントだけでは具体的なイメージを掴みづらく、実際に手を動かして試行錯誤する必要がありました。
Slack ワークフローとCustom functionを組み合わせれば、多くの方が日常的に利用するSlackから直接外部システムへデータを簡単に渡せるため、手動での作業が減らすことができます。
また、このSlack ワークフローとCustom functionを組み合わせることで、インシデント管理以外でも幅広い分野で活用が考えられます。

活用例

  • 勤怠システムの打刻や報告
  • ワークフローで取得した値をCustom function で生成AIのAPIにリクエストし、情報を付け加えてもらったり、要約してもらう。

上記に挙げた例以外の多くの活用方法があると思います。今後は社内の課題に合わせて、Custom functionで解決できる方法を模索していきたいです。


株式会社hacomonoでは一緒に働く仲間を募集しています。
エンジニア採用サイトや採用ウィッシュリストもぜひご覧ください!