Google Chat 앱을 웹훅으로 빌드하기

이 페이지에서는 외부 트리거를 사용하여 Chat 스페이스에 비동기 메시지를 전송하는 Webhook을 설정하는 방법을 설명합니다. 예를 들어 서버가 다운될 때 Chat의 긴급 대기 엔지니어에게 알리도록 모니터링 애플리케이션을 구성하는 것이 가능합니다. Chat 앱으로 동기식 메시지를 보내려면 메시지 보내기를 참고하세요.

이 유형의 아키텍처 설계에서는 통신이 일방적이므로 사용자가 webhook 또는 연결된 외부 애플리케이션과 상호작용할 수 없습니다. 웹훅은 대화형이 아닙니다. 사용자 또는 Chat 앱 상호작용 이벤트의 메시지에 응답하거나 메시지를 수신할 수 없습니다. 메시지에 응답하려면 웹후크 대신 Chat 앱을 빌드하세요.

웹훅은 기술적으로 Chat 앱이 아닙니다. 웹훅은 표준 HTTP 요청을 사용하여 애플리케이션을 연결하지만 이 페이지에서는 편의상 웹훅을 Chat 앱이라고 합니다. 각 웹훅은 등록된 Chat 스페이스에서만 작동합니다. 수신 웹훅은 모든 사용자가 Chat 앱을 사용 설정한 경우에만 채팅 메시지에서 작동합니다. Google Workspace Marketplace에 Webhook을 게시할 수 없습니다.

다음 다이어그램은 Chat에 연결된 Webhook의 아키텍처를 보여줍니다.

Chat에 비동기 메시지를 보내는 수신 웹훅의 아키텍처

위 다이어그램에서 Chat 앱에는 다음과 같은 정보 흐름이 있습니다.

  1. Chat 앱 로직은 프로젝트 관리 시스템 또는 티켓 판매 도구와 같은 외부 서드 파티 서비스에서 정보를 수신합니다.
  2. Chat 앱 로직은 특정 Chat 스페이스에 웹훅 URL을 사용하여 메시지를 보낼 수 있는 클라우드 또는 온프레미스 시스템에 호스팅됩니다.
  3. 사용자는 해당 Chat 스페이스에서 Chat 앱의 메시지를 받을 수 있지만 Chat 앱과 상호작용할 수는 없습니다.

기본 요건

Python

  • Google Chat에 액세스할 수 있는 비즈니스 또는 엔터프라이즈 Google Workspace 계정 Google Workspace 조직에서 사용자가 수신 웹훅을 추가하고 사용하도록 허용해야 합니다.
  • Python 3.6 이상
  • pip 패키지 관리 도구
  • httplib2 라이브러리 라이브러리를 설치하려면 명령줄 인터페이스에서 다음 명령어를 실행합니다.

    pip install httplib2
  • Google Chat 스페이스 Google Chat API를 사용하여 스페이스를 만들려면 스페이스 만들기를 참고하세요. Chat에서 템플릿을 만들려면 고객센터 문서를 참고하세요.

Node.js

자바

Apps Script

웹훅 만들기

웹훅을 만들려면 메시지를 수신할 Chat 스페이스에 웹훅을 등록한 다음 메시지를 전송하는 스크립트를 작성합니다.

수신 웹훅 등록

  1. 브라우저에서 Chat을 엽니다. Chat 모바일 앱에서는 웹훅을 구성할 수 없습니다.
  2. Webhook을 추가할 스페이스로 이동합니다.
  3. 스페이스 제목 옆에 있는 확장 화살표를 클릭한 다음 앱 및 통합을 클릭합니다.
  4. 웹훅 추가를 클릭합니다.

  5. 이름 필드에 Quickstart Webhook를 입력합니다.

  6. 아바타 URL 필드에 https://developers.google.com/chat/images/chat-product-icon.png를 입력합니다.

  7. 저장을 클릭합니다.

  8. 웹훅 URL을 복사하려면 더보기를 클릭한 다음 링크 복사를 클릭합니다.

웹훅 스크립트 작성

웹훅 스크립트 예시는 웹훅 URL에 POST 요청을 전송하여 웹훅이 등록된 스페이스에 메시지를 전송합니다. Chat API는 Message 인스턴스로 응답합니다.

웹훅 스크립트를 만드는 방법을 알아보려면 언어를 선택하세요.

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 변수의 값을 웹훅을 등록할 때 복사한 웹훅 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 변수의 값을 웹훅을 등록할 때 복사한 웹훅 URL로 바꿉니다.

자바

  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 변수의 값을 웹훅을 등록할 때 복사한 웹훅 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 변수의 값을 웹훅을 등록할 때 복사한 웹훅 URL로 바꿉니다.

웹훅 스크립트 실행

CLI에서 다음 스크립트를 실행합니다.

Python

  python3 quickstart.py

Node.js

  node index.js

자바

  mvn compile exec:java -Dexec.mainClass=App

Apps Script

  • 실행을 클릭합니다.

코드를 실행하면 웹훅이 등록된 스페이스에 메시지를 전송합니다.

메시지 대화목록 시작 또는 답장하기

  1. 메시지 요청 본문의 일부로 spaces.messages.thread.threadKey를 지정합니다. 대화목록을 시작하는지 아니면 대화목록에 답장하는지에 따라 threadKey에 다음 값을 사용합니다.

    • 대화목록을 시작하는 경우 threadKey를 임의의 문자열로 설정하지만 대화목록에 답글을 게시할 때 이 값을 기록해 두세요.

    • 대화목록에 답장하는 경우 대화목록이 시작될 때 설정된 threadKey를 지정합니다. 예를 들어 초기 메시지에서 MY-THREAD를 사용한 대화목록에 답글을 게시하려면 MY-THREAD를 설정합니다.

  2. 지정된 threadKey를 찾을 수 없는 경우 스레드 동작을 정의합니다.

    • 대화목록에 답장하거나 새 대화목록을 시작합니다. messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD 매개변수를 웹훅 URL에 추가합니다. 이 URL 매개변수를 전달하면 Chat에서 지정된 threadKey를 사용하여 기존 대화목록을 찾습니다. 대화목록이 발견되면 해당 대화목록에 답장으로 메시지가 게시됩니다. 찾을 수 없는 경우 메시지는 해당 threadKey에 해당하는 새 스레드를 시작합니다.

    • 대화목록에 답장하거나 아무 조치도 취하지 않습니다. messageReplyOption=REPLY_MESSAGE_OR_FAIL 매개변수를 webhook URL에 추가합니다. 이 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 메시지 전송이 중요하지 않은 경우에 적합합니다.

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에서 웹훅을 사용하여 메시지를 만들면 응답에 전체 메시지가 포함되지 않습니다. 응답에는 namethread.name 필드만 채워집니다.
  • Webhook에는 스페이스의 모든 Chat 앱 간에 공유되는 spaces.messages.create 스페이스당 쿼리 제한(60초당 60개)이 적용됩니다. Chat API 할당량에 관한 자세한 내용은 사용 한도를 참고하세요.