Using Cloud Pub/Sub as an endpoint for your app

When creating an app for your organization, your organization may be using a firewall, which can prevent Google Chat from sending messages to your app. To help with this, Google Chat features an integration with Cloud Pub/Sub. This integration lets your app establish a connection to Cloud Pub/Sub and receive messages.

Architecture of Cloud Pub/Sub integration with Chat.

Setting up Cloud Pub/Sub

This section describes how to set up a Cloud Pub/Sub topic and subscribe your app to it.

Create a Pub/Sub-enabled Google Cloud project

To use Cloud Pub/Sub with your app, you need to create a Google Cloud Platform project that has the Pub/Sub API enabled.

  1. Create a new project in the Google Cloud console.
  2. In the left pane of the console, select Pub/Sub and then select Enable API.

Create a Pub/Sub topic

Next, create a topic that the Chat API should send messages to. See the Pub/Sub Console quickstart to see how to create a topic.

Enable the Google Chat API

Make sure to enable the Google Chat API and set it to the topic created in the previous step.

  1. In the left pane of the console, select APIs & Services and enable the Google Chat API.
  2. When configuring the API, make sure the Connection Settings are set to Cloud Pub/Sub and provide the same topic from the previous step.
  3. Fill out the other fields as detailed in the sample app Guide.

Grant publish rights on your topic

In order for Google Chat to publish messages to your topic, it must have publishing rights to the topic. To grant Google Chat these permissions, assign the Pub/Sub Publisher role to the following service account:

chat-api-push@system.gserviceaccount.com

This will authorize Google Chat to publish on your topic.

Create a service account

In order for your app code to authorize with Cloud Pub/Sub and Google Chat, it needs to use a service account. To set up a service account, see Authorize and authenticate with Chat API using a service account.

Create a subscription

Follow the Cloud Pub/Sub Subscriber Guide to set up a subscription to the topic that you created. Configure the subscription type to be a webhook pull. Add permissions on the subscription for the service account you created in the last step and give it 'Pub/Sub Subscriber' Role.

Publish the Cloud Pub/Sub app

See the Publishing apps guide for details of how to publish your app. For Cloud Pub/Sub apps, select the option for Cloud Pub/Sub and enter the fully qualified name of the topic created. The topic name must be in the following format:

projects/PROJECT_ID/topics/TOPIC_ID

For example, projects/pubsub-demo-2/topics/test-topic

Once you've published the app in this way, users will be able to use it and the app will receive messages on the Pub/Sub topic that you configured.

The example in the next section shows how to create and run a simple app that implements these objects.

Example app implementation

The sample code below shows a simple app that uses Cloud Pub/Sub to receive incoming messages. To try out this app, you need to:

  • Edit the project Id and subscription ID values in the Main class
  • Provide service account credentials as described in the Getting Started with Authentication guide.

    export GOOGLE_APPLICATIONCREDENTIALS=<path_to_service_account_file>

To run this code, you'll need the following dependencies in your classpath:

package com.google.chat;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpTransport;
import com.google.cloud.pubsub.v1.AckReplyConsumer;
import com.google.cloud.pubsub.v1.MessageReceiver;
import com.google.cloud.pubsub.v1.Subscriber;
import com.google.pubsub.v1.PubsubMessage;
import com.google.pubsub.v1.ProjectSubscriptionName;
import java.io.FileInputStream;
import java.util.Collections;

public class Main {

  public static final String CREDENTIALS_PATH_ENV_PROPERTY = "GOOGLE_APPLICATION_CREDENTIALS";

  // Google Cloud Project ID
  public static final String PROJECT_ID = "my-project-id";

  // Cloud Pub/Sub Subscription ID
  public static final String SUBSCRIPTION_ID = "my-subscription-id";

  public static void main(String[] args) throws Exception {
    ProjectSubscriptionName subscriptionName =
        ProjectSubscriptionName.of(PROJECT_ID, SUBSCRIPTION_ID);

    // Instantiate app, which implements an asynchronous message receiver.
    EchoApp echoApp = new EchoApp();

    // Create a subscriber for "my-subscription-id" bound to the message receiver
    final Subscriber subscriber =
        Subscriber.newBuilder(subscriptionName, echoApp).build();
    System.out.println("Starting subscriber...");
    subscriber.startAsync();

    // Wait for termination
    subscriber.awaitTerminated();
  }
}

/**
 * A demo app which implements {@link MessageReceiver} to receive messages. It simply echoes the
 * incoming messages.
 */
class EchoApp implements MessageReceiver {

  // Path to the private key JSON file of the service account to be used for posting response
  // messages to Google Chat.
  // In this demo, we are using the same service account for authorizing with Cloud Pub/Sub to
  // receive messages and authorizing with Google Chat to post messages. If you are using
  // different service accounts, please set the path to the private key JSON file of the service
  // account used to post messages to Google Chat here.
  private static final String SERVICE_ACCOUNT_KEY_PATH =
      System.getenv(Main.CREDENTIALS_PATH_ENV_PROPERTY);

  // Developer code for Google Chat api scope.
  private static final String GOOGLE_CHAT_API_SCOPE = "https://www.googleapis.com/auth/chat.bot";

  // Response URL Template with placeholders for space id.
  private static final String RESPONSE_URL_TEMPLATE =
      "https://chat.googleapis.com/v1/__SPACE_ID__/messages";

  // Response echo message template.
  private static final String RESPONSE_TEMPLATE = "You said: `__MESSAGE__`";

  private static final String ADDED_RESPONSE = "Thank you for adding me!";

  GoogleCredential credential;
  HttpTransport httpTransport;
  HttpRequestFactory requestFactory;

  EchoApp() throws Exception {
    credential =
        GoogleCredential.fromStream(new FileInputStream(SERVICE_ACCOUNT_KEY_PATH))
            .createScoped(Collections.singleton(GOOGLE_CHAT_API_SCOPE));
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    requestFactory = httpTransport.createRequestFactory(credential);
  }

  // Called when a message is received by the subscriber.
  @Override
  public void receiveMessage(PubsubMessage pubsubMessage, AckReplyConsumer consumer) {
    System.out.println("Id : " + pubsubMessage.getMessageId());
    // handle incoming message, then ack/nack the received message
    try {
      ObjectMapper mapper = new ObjectMapper();
      JsonNode dataJson = mapper.readTree(pubsubMessage.getData().toStringUtf8());
      System.out.println("Data : " + dataJson.toString());
      handle(dataJson);
      consumer.ack();
    } catch (Exception e) {
      System.out.println(e);
      consumer.nack();
    }
  }

  public void handle(JsonNode eventJson) throws Exception {
    JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(false);
    ObjectNode responseNode = jsonNodeFactory.objectNode();

    // Construct the response depending on the event received.

    String eventType = eventJson.get("type").asText();
    switch (eventType) {
      case "ADDED_TO_SPACE":
        responseNode.put("text", ADDED_RESPONSE);
        // An app can also be added to a space by @mentioning it in a message. In that case, we fall
        // through to the MESSAGE case and let the app respond. If the app was added using the
        // invite flow, we just post a thank you message in the space.
        if(!eventJson.has("message")) {
          break;
        }
      case "MESSAGE":
        responseNode.put("text",
            RESPONSE_TEMPLATE.replaceFirst(
                "__MESSAGE__", eventJson.get("message").get("text").asText()));
        // In case of message, post the response in the same thread.
        ObjectNode threadNode = jsonNodeFactory.objectNode();
        threadNode.put("name", eventJson.get("message").get("thread").get("name").asText());
        responseNode.put("thread", threadNode);
        break;
      case "REMOVED_FROM_SPACE":
      default:
        // Do nothing
        return;
    }

    // Post the response to Google Chat.

    String URI =
        RESPONSE_URL_TEMPLATE.replaceFirst(
            "__SPACE_ID__", eventJson.get("space").get("name").asText());
    GenericUrl url = new GenericUrl(URI);

    HttpContent content =
        new ByteArrayContent("application/json", responseNode.toString().getBytes("UTF-8"));
    HttpRequest request = requestFactory.buildPostRequest(url, content);
    com.google.api.client.http.HttpResponse response = request.execute();
  }
}