Google Chat アプリを Webhook として作成する

このページでは、外部トリガーを使用して Chat スペースに非同期メッセージを送信する Webhook を設定する方法について説明します。たとえば、サーバーが停止したときに Chat でオンコール担当者に通知するようにモニタリング アプリケーションを構成できます。Chat アプリで同期メッセージを送信するには、メッセージを送信するをご覧ください。

このタイプのアーキテクチャ設計では、通信が単方向であるため、ユーザーは Webhook や接続された外部アプリケーションを操作できません。Webhook は会話型ではありません。ユーザーからのメッセージや Chat 用アプリのインタラクション イベントに返信したり、受信したりすることはできません。メッセージに返信するには、Webhook ではなく Chat アプリを構築します。

技術的には、Webhook は Chat アプリではありません(Webhook は標準の HTTP リクエストを使用してアプリを接続します)。このページでは、わかりやすくするために Chat アプリと呼んでいます。各 Webhook は、登録されている Chat スペースでのみ機能します。受信 Webhook はダイレクト メッセージで機能しますが、すべてのユーザーが Chat アプリを有効にしている場合に限られます。Webhook を Google Workspace Marketplace に公開することはできません。

次の図は、Chat に接続された Webhook のアーキテクチャを示しています。

着信 Webhook が Chat に非同期メッセージを送信するアーキテクチャ。

上の図では、Chat アプリの情報フローは次のとおりです。

  1. Chat アプリのロジックは、プロジェクト管理システムやチケット販売ツールなどの外部サードパーティ サービスから情報を受け取ります。
  2. Chat アプリのロジックは、Webhook URL を使用して特定の Chat スペースにメッセージを送信できるクラウドまたはオンプレミス システムでホストされます。
  3. ユーザーは、その特定の Chat スペースの Chat アプリからメッセージを受信できますが、Chat アプリを操作することはできません。

前提条件

Python

  • Google Chat へのアクセス権を持つ Business または Enterprise の Google Workspace アカウント。Google Workspace 組織で、ユーザーが着信 Webhook を追加して使用できるようにする必要があります。
  • Python 3.6 以降
  • pip パッケージ管理ツール
  • httplib2 ライブラリ。ライブラリをインストールするには、コマンドライン インターフェースで次のコマンドを実行します。

    pip install httplib2
  • Google Chat のスペース。Google Chat API を使用して作成するには、スペースを作成するをご覧ください。Chat で作成するには、ヘルプセンターのドキュメントをご覧ください。

Node.js

Java

Apps Script

Webhook を作成する

Webhook を作成するには、メッセージを受信する Chat スペースに登録し、メッセージを送信するスクリプトを作成します。

着信 Webhook を登録する

  1. ブラウザで Chat を開きます。Webhook は Chat モバイルアプリから設定できません。
  2. Webhook を追加するスペースに移動します。
  3. スペースのタイトルの横にある 展開矢印をクリックし、[アプリとインテグレーション] をクリックします。
  4. [Webhook を追加] をクリックします。

  5. [名前] フィールドに「Quickstart Webhook」と入力します。

  6. [アバターの URL] フィールドに「https://developers.google.com/chat/images/chat-product-icon.png」と入力します。

  7. [保存] をクリックします。

  8. Webhook URL をコピーするには、 [その他]、 [リンクをコピー] の順にクリックします。

Webhook スクリプトを作成する

Webhook スクリプトの例では、Webhook URL に POST リクエストを送信して、Webhook が登録されているスペースにメッセージを送信します。Chat API は Message のインスタンスで応答します。

Webhook スクリプトの作成方法を確認する言語を選択してください。

Python

  1. 作業ディレクトリに quickstart.py という名前のファイルを作成します。

  2. quickstart.py に次のコードを貼り付けます。

    python/webhook/quickstart.py
    from json import dumps
    from httplib2 import Http
    
    # Copy the webhook URL from the Chat space where the webhook is registered.
    # The values for SPACE_ID, KEY, and TOKEN are set by Chat, and are included
    # when you copy the webhook URL.
    
    def main():
        """Google Chat incoming webhook quickstart."""
        url = "https://chat.googleapis.com/v1/spaces/SPACE_ID/messages?key=KEY&token=TOKEN"
        app_message = {"text": "Hello from a Python script!"}
        message_headers = {"Content-Type": "application/json; charset=UTF-8"}
        http_obj = Http()
        response = http_obj.request(
            uri=url,
            method="POST",
            headers=message_headers,
            body=dumps(app_message),
        )
        print(response)
    
    
    if __name__ == "__main__":
        main()
  3. url 変数の値は、Webhook の登録時にコピーした Webhook URL に置き換えます。

Node.js

  1. 作業ディレクトリに index.js という名前のファイルを作成します。

  2. index.js に次のコードを貼り付けます。

    node/webhook/index.js
    /**
     * Sends asynchronous message to Google Chat
     * @return {Object} response
     */
    async function webhook() {
      const url = "https://chat.googleapis.com/v1/spaces/SPACE_ID/messages"
      const res = await fetch(url, {
        method: "POST",
        headers: {"Content-Type": "application/json; charset=UTF-8"},
        body: JSON.stringify({text: "Hello from a Node script!"})
      });
      return await res.json();
    }
    
    webhook().then(res => console.log(res));
  3. url 変数の値を、Webhook の登録時にコピーした Webhook URL に置き換えます。

Java

  1. 作業ディレクトリに pom.xml という名前のファイルを作成します。

  2. pom.xml に、次のコードをコピーして貼り付けます。

    java/webhook/pom.xml
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>com.google.chat.webhook</groupId>
      <artifactId>java-webhook-app</artifactId>
      <version>0.1.0</version>
    
      <name>java-webhook-app</name>
      <url>https://github.com/googleworkspace/google-chat-samples/tree/main/java/webhook</url>
    
      <properties>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
      </properties>
    
      <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.9.1</version>
        </dependency>
      </dependencies>
    
      <build>
        <pluginManagement>
          <plugins>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.0</version>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </project>
  3. 作業ディレクトリに、次のディレクトリ構造 src/main/java を作成します。

  4. src/main/java ディレクトリに App.java というファイルを作成します。

  5. App.java に次のコードを貼り付けます。

    java/webhook/src/main/java/com/google/chat/webhook/App.java
    import com.google.gson.Gson;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    import java.util.Map;
    import java.net.URI;
    
    public class App {
      private static final String URL = "https://chat.googleapis.com/v1/spaces/AAAAGCYeSRY/messages";
      private static final Gson gson = new Gson();
      private static final HttpClient client = HttpClient.newHttpClient();
    
      public static void main(String[] args) throws Exception {
        String message = gson.toJson(Map.of("text", "Hello from Java!"));
    
        HttpRequest request = HttpRequest.newBuilder(URI.create(URL))
          .header("accept", "application/json; charset=UTF-8")
          .POST(HttpRequest.BodyPublishers.ofString(message)).build();
    
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    
        System.out.println(response.body());
      }
    }
  6. URL 変数の値は、Webhook の登録時にコピーした Webhook URL に置き換えます。

Apps Script

  1. ブラウザで Apps Script にアクセスします。

  2. [新しいプロジェクト] をクリックします。

  3. 次のコードを貼り付けます。

    apps-script/webhook/webhook.gs
    function webhook() {
      const url = "https://chat.googleapis.com/v1/spaces/SPACE_ID/messages"
      const options = {
        "method": "post",
        "headers": {"Content-Type": "application/json; charset=UTF-8"},
        "payload": JSON.stringify({"text": "Hello from Apps Script!"})
      };
      const response = UrlFetchApp.fetch(url, options);
      console.log(response);
    }
  4. url 変数の値は、Webhook の登録時にコピーした Webhook URL に置き換えます。

Webhook スクリプトを実行する

CLI でスクリプトを実行します。

Python

  python3 quickstart.py

Node.js

  node index.js

Java

  mvn compile exec:java -Dexec.mainClass=App

Apps Script

  • [実行] をクリックします。

コードを実行すると、webhook は登録したスペースにメッセージを送信します。

メッセージ スレッドを開始または返信する

  1. メッセージ リクエスト本文の一部として spaces.messages.thread.threadKey を指定します。スレッドを開始する場合とスレッドに返信する場合に応じて、threadKey に次の値を使用します。

    • スレッドを開始する場合は、threadKey を任意の文字列に設定しますが、スレッドに返信を投稿するためにこの値をメモしておきます。

    • スレッドに返信する場合は、スレッドの開始時に設定された threadKey を指定します。たとえば、最初のメッセージで MY-THREAD を使用したスレッドに返信を投稿するには、MY-THREAD を設定します。

  2. 指定された threadKey が見つからない場合のスレッドの動作を定義します。

    • スレッドに返信するか、新しいスレッドを開始します。messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD パラメータを Webhook URL に追加します。この URL パラメータを渡すと、Chat は指定された threadKey を使用して既存のスレッドを検索します。見つかった場合は、そのスレッドへの返信としてメッセージが投稿されます。見つからない場合、メッセージはその threadKey に対応する新しいスレッドを開始します。

    • スレッドに返信するか、何もしないかを選択します。Webhook URL に messageReplyOption=REPLY_MESSAGE_OR_FAIL パラメータを追加します。この URL パラメータを渡すと、Chat は指定された threadKey を使用して既存のスレッドを検索します。見つかった場合は、そのスレッドへの返信としてメッセージが投稿されます。見つからない場合、メッセージは送信されません。

    詳しくは、messageReplyOption をご覧ください。

次のコードサンプルは、メッセージ スレッドを開始または返信します。

Python

python/webhook/thread-reply.py
from json import dumps
from httplib2 import Http

# Copy the webhook URL from the Chat space where the webhook is registered.
# The values for SPACE_ID, KEY, and TOKEN are set by Chat, and are included
# when you copy the webhook URL.
#
# Then, append messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD to the
# webhook URL.


def main():
    """Google Chat incoming webhook that starts or replies to a message thread."""
    url = "https://chat.googleapis.com/v1/spaces/SPACE_ID/messages?key=KEY&token=TOKEN&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
    app_message = {
        "text": "Hello from a Python script!",
        # To start a thread, set threadKey to an arbitratry string.
        # To reply to a thread, specify that thread's threadKey value.
        "thread": {"threadKey": "THREAD_KEY_VALUE"},
    }
    message_headers = {"Content-Type": "application/json; charset=UTF-8"}
    http_obj = Http()
    response = http_obj.request(
        uri=url,
        method="POST",
        headers=message_headers,
        body=dumps(app_message),
    )
    print(response)


if __name__ == "__main__":
    main()

Node.js

node/webhook/thread-reply.js
/**
 * Sends asynchronous message to Google Chat
 * @return {Object} response
 */
async function webhook() {
  const url = "https://chat.googleapis.com/v1/spaces/SPACE_ID/messages?key=KEY&token=TOKEN&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
  const res = await fetch(url, {
    method: "POST",
    headers: {"Content-Type": "application/json; charset=UTF-8"},
    body: JSON.stringify({
      text: "Hello from a Node script!",
      thread: {threadKey: "THREAD_KEY_VALUE"}
    })
  });
  return await res.json();
}

webhook().then(res => console.log(res));

Apps Script

apps-script/webhook/thread-reply.gs
function webhook() {
  const url = "https://chat.googleapis.com/v1/spaces/SPACE_ID/messages?key=KEY&token=TOKEN&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
  const options = {
    "method": "post",
    "headers": {"Content-Type": "application/json; charset=UTF-8"},
    "payload": JSON.stringify({
      "text": "Hello from Apps Script!",
      "thread": {"threadKey": "THREAD_KEY_VALUE"}
    })
  };
  const response = UrlFetchApp.fetch(url, options);
  console.log(response);
}

エラーを処理する

Webhook リクエストは、次のようなさまざまな理由で失敗することがあります。

  • リクエストが無効です。
  • Webhook または Webhook をホストするスペースが削除された。
  • ネットワーク接続や割り当て上限などの断続的な問題。

Webhook を作成する際は、次のようにエラーを適切に処理する必要があります。

Google Chat API は、エラーを google.rpc.Status として返します。これには、発生したエラーのタイプ(クライアント エラー(400 シリーズ)またはサーバーエラー(500 シリーズ))を示す HTTP エラー code が含まれます。すべての HTTP マッピングを確認するには、google.rpc.Code をご覧ください。

{
    "code": 503,
    "message": "The service is currently unavailable.",
    "status": "UNAVAILABLE"
}

HTTP ステータス コードを解釈してエラーを処理する方法については、エラーをご覧ください。

制限事項と考慮事項

  • Google Chat API で Webhook を使用してメッセージを作成すると、レスポンスにメッセージ全体が含まれません。レスポンスには name フィールドと thread.name フィールドのみが入力されます。
  • Webhook には、spaces.messages.create のスペースごとの割り当て(60 秒あたり 60 件のリクエスト)が適用されます。この割り当ては、スペース内のすべての Webhook で共有されます。また、同じスペースで 1 秒あたり 1 件のクエリを超える Webhook リクエストが拒否されることもあります。Chat API の割り当てについて詳しくは、使用制限をご覧ください。