Creating new bots  |  Google Chat API  |  Google Developers

This page describes how you can create new bots that receive, process, and
respond to events from Google Chat:

  • Receive messages and other kinds of events generated by Google Chat
  • Send event responses and other messages into Google Chat

Endpoint types

Events from Google Chat are delivered to your bot via an endpoint, of which
there are different types:

  • HTTPS endpoints present your bot as a web service. You’ll need to set up a
    web server to use as an interface for your bot’s implementation. Your bot
    can respond synchronously or asynchronously
    to these events.
  • Google Cloud Pub/Sub endpoints use a topic
    on Google Cloud Pub/Sub to relay an event to your bot’s implementation. This
    is useful when your implementation is behind a firewall. Bots that use pub/sub
    endpoints can only respond asynchronously.
  • DialogFlow endpoints let your bot utilize the natural language processing (NLP)
    capabilities of DialogFlow. Please see DialogFlow documentation for details.

For a simple, straightforward bot architecture, try implementing a bot using an
HTTPS endpoint (a web service, essentially) that responds synchronously, always
enclosing its payload in the HTTPS POST response. This approach does not involve
authorization, so it doesn’t need a service account. See the simple bot
implementation section below for an example of this style of bot.

You may need to take a more complex approach if your bot is behind a firewall or
sends unsolicited messages such as alarms or other notifications to Google
Chat.

tl;dr… A very simple bot implementation

The following code implements a simple bot in Python using the
Flask web framework.

#!/usr/bin/env python3
"""Example bot that returns a synchronous response."""

from flask import Flask, request, json


app = Flask(__name__)


@app.route('/', methods=['POST'])
def on_event():
  """Handles an event from Google Chat."""
  event = request.get_json()
  if event['type'] == 'ADDED_TO_SPACE' and not event['space']['singleUserBotDm']:
    text = 'Thanks for adding me to "%s"!' % (event['space']['displayName'] if event['space']['displayName'] else 'this chat')
  elif event['type'] == 'MESSAGE':
    text = 'You said: `%s`' % event['message']['text']
  else:
    return
  return json.jsonify({'text': text})


if __name__ == '__main__':
  app.run(port=8080, debug=True)

Because it’s a web service, the bot presents an HTTPS endpoint and doesn’t need
to use Cloud Pub/Sub to relay events to it. And because it always returns its
response payload within the JSON response, it doesn’t need to authenticate
using a service account.

Handling events from Google Chat

This section describes how to receive and process events that your bot receives
from Google Chat.

Registering the bot

Before your bot can receive events from Google Chat, you must specify its
endpoint in the Chat API configuration tab when you publish your bot.

Once you’ve registered the endpoint and published your bot, Google Chat will
recognize events addressed to your bot and dispatch them to the specified
endpoint.

Verifying bot authenticity

Once you’ve registered your HTTPS
bot, you
need a way for your implementation to verify that the request is actually
coming from Google.

Google Chat includes a bearer token in the Authorization header of every HTTPS Request to a bot. For example:

POST
Host: yourboturl.com
Authorization: Bearer AbCdEf123456
Content-Type: application/json
User-Agent: Google-Dynamite

The string AbCdEf123456 in the example above is the bearer authorization token.
This is a cryptographic token produced by Google. You can verify your bearer token using
an open source Google API client library:

All bearer tokens sent with requests from Google chat will have
chat@system.gserviceaccount.com as the issuee, with the audience field specifying the target bot’s project number from the
Google API Console.
For example, if the request is for a bot with the project number 1234567890, then the
audience is 1234567890.

You should verify that the request is coming from Google
and is intended for the target bot. If the token doesn’t verify, the bot should
respond to the request with an HTTPS response code 401 (Unauthorized).

Java

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;

/** Tool for verifying JWT Tokens for Bots in Google Chat. */
public class JWTVerify {
  // Bearer Tokens received by bots will always specify this issuer.
  static String CHAT_ISSUER = "chat@system.gserviceaccount.com";

  // Url to obtain the public certificate for the issuer.
  static String PUBLIC_CERT_URL_PREFIX =
      "https://www.googleapis.com/service_accounts/v1/metadata/x509/";

  // Intended audience of the token, which will be the project number of the bot.
  static String AUDIENCE = "1234567890";

  // Get this value from the request's Authorization HTTPS header.
  // For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456"
  static String BEARER_TOKEN = "AbCdEf123456";

  public static void main(String[] args) throws GeneralSecurityException, IOException {
    JsonFactory factory = new JacksonFactory();

    GooglePublicKeysManager.Builder keyManagerBuilder =
        new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory);

    String certUrl = PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER;
    keyManagerBuilder.setPublicCertsEncodedUrl(certUrl);

    GoogleIdTokenVerifier.Builder verifierBuilder =
        new GoogleIdTokenVerifier.Builder(keyManagerBuilder.build());
    verifierBuilder.setIssuer(CHAT_ISSUER);
    GoogleIdTokenVerifier verifier = verifierBuilder.build();

    GoogleIdToken idToken = GoogleIdToken.parse(factory, BEARER_TOKEN);
    if (idToken == null) {
      System.out.println("Token cannot be parsed");
      System.exit(-1);
    }

    // Verify valid token, signed by CHAT_ISSUER.
    if (!verifier.verify(idToken)
        || !idToken.verifyAudience(Collections.singletonList(AUDIENCE))
        || !idToken.verifyIssuer(CHAT_ISSUER)) {
      System.out.println("Invalid token");
      System.exit(-1);
    }

    // Token originates from Google and is targeted to a specific client.
    System.out.println("The token is valid");
  }
}

Python

import sys

from oauth2client import client

# Bearer Tokens received by bots will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

# Url to obtain the public certificate for the issuer.
PUBLIC_CERT_URL_PREFIX = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/'

# Intended audience of the token, which will be the project number of the bot.
AUDIENCE = '1234567890'

# Get this value from the request's Authorization HTTPS header.
# For example, for 'Authorization: Bearer AbCdEf123456' use 'AbCdEf123456'.
BEARER_TOKEN = 'AbCdEf123456'

try:
  # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  token = client.verify_id_token(
      BEARER_TOKEN, AUDIENCE, cert_uri=PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER)

  if token['iss'] != CHAT_ISSUER:
    sys.exit('Invalid issuee')
except:
  sys.exit('Invalid token')

# Token originates from Google and is targeted to a specific client.
print 'The token is valid'

Event payload

When your bot receives an event from Google Chat, the event includes a
request body: this is the JSON payload that represents the event. The request
body always includes the following information:

  • type: A string that specifies the type of the event.
  • eventTime: A string containing the event timestamp.

Additional information contained in the request body depends on the event type.
The following example shows a possible payload:

{
  "type": "MESSAGE",
  "eventTime": "2017-03-02T19:02:59.910959Z",
  "space": {
    "name": "spaces/AAAAAAAAAAA",
    "displayName": "Best Dogs Discussion Space",
    "type": "ROOM"
  },
  "message": {
    "name": "spaces/AAAAAAAAAAA/messages/CCCCCCCCCCC",
    "sender": {
      "name": "users/12345678901234567890",
      "displayName": "Chris Corgi",
      "avatarUrl": "https://lh3.googleusercontent.com/.../photo.jpg",
      "email": "chriscorgi@example.com"
    },
    "createTime": "2017-03-02T19:02:59.910959Z",
    "text": "I mean is there any good reason their legs should be longer?",
    "thread": {
      "name": "spaces/AAAAAAAAAAA/threads/BBBBBBBBBBB"
    }
  }
}

See the event formats
reference for details of the different event types and their request formats.

Processing the event

When your bot receives an event from Google Chat, what it does with that
event is completely implementation dependent. The bot may look up some
information from a data source, record the event information, or just about
anything else. This processing behavior is essentially what defines the bot.

In most cases, a bot will not only process the information contained in the
event, but will generate a response back to the thread that issued the event.
The following diagram describes a typical interaction with a bot in a chat
space:

There are three kinds of events shown in the above diagram: ADDED_TO_SPACE,
MESSAGE, and REMOVED_FROM_SPACE. A bot can’t respond after being removed
from a space, but it can respond to the other two types.

Responding synchronously

A bot can respond to an event synchronously by returning a JSON-formatted
message payload in the HTTPS response. The deadline for a synchronous response is
30 seconds.

A synchronous response from a bot is always posted in the thread that generated
the event to the bot.

Responding asynchronously

If a bot needs to respond to a user message beyond the 30-second deadline (for
example, it may need to report back after completing a long-running task), it
can respond asynchronously. This is exactly like sending a spontaneous message
as described in the into an existing thread section.

Lightweight bots that don’t use service accounts cannot respond asynchronously.

Retry

If an HTTPS request to your bot fails (e.g. timeout, temporary network failure,
or a non-2xx HTTPS status code), Google Chat will additionally retry delivery
twice, with at least a ten-second delay between each retry. As a result, a bot
may receive the same message up to three times in certain situations.
No retry is attempted if the request completes successfully but returns an
invalid message payload.

Bot-initiated messages

This section describes how bots can send arbitrary messages into a space.

Many bots send messages only in direct response to an event that they receive
from Google Chat. However, some bots might send messages when triggered by
other things, for example:

  • A time-based alarm like a calendar event
  • A change in state of some relevant data
  • The completion of a remote process

This section describes how to send these messages from your app to Google
Chat.

Into an existing thread

To send a message as a reply in an existing thread, specify the thread’s ID
in the message payload as shown below:

{
  "text": "...",
  "thread": {
     "name": "spaces/SPACE_ID/threads/THREAD_ID"
  }
}

The specific THREAD_ID is available in the payload of MESSAGE
events that your bot receives from Google Chat. Keep track of this ID so that
the bot can inject messages into the thread.

As a new thread

To send a message into Google Chat as a new thread, your bot should omit the
thread ID, as shown below:

https://chat.googleapis.com/v1/spaces/SPACE_ID/messages

Requests must specify Content-Type: application/json in the request header.
See the Google Chat API Message Format
reference for the JSON format of Google Chat messages.
The following example shows a simple request using cURL:

curl -X POST 
    -H 'Content-Type: application/json' 
    'https://chat.googleapis.com/....' 
    -d '{"text": "Hello!"}'

Thread key

In many cases, bots may want to post multiple messages related to the same
entity into the same thread. For example, a bug tracker integration may
want to post all notification messages related to the same bug into the same
thread.

To achieve this, bots can specify an arbitrary thread key in each request.
Messages posted with the same thread key will be grouped into the same
thread. For example, the example bug tracker integration above might use the
bug ID as part of a consistent thread key. The first notification message for a
bug will then create a new thread; all subsequent messages for the same bug will
be posted into that same thread.

The thread key is specified in the threadKey query parameter in an
inbound HTTPS request. For instance:

https://chat.googleapis.com/v1/spaces/SPACE_ID/messages?
    threadKey=ARBITRARY_STRING

Thread keys are also scoped to a specific bot; if two different bots happen
to both post messages using the same thread key, those two messages will
not be grouped into the same thread.

Related Articles

Leave a Reply

Back to top button