Build a Google Chat app behind a firewall with Pub/Sub

  • Build a Google Chat app that uses Pub/Sub for asynchronous communication, ideal for scenarios with firewall restrictions or when utilizing the Google Workspace Events API.

  • The Chat app cannot use dialogs and card updates are limited to patching the entire message; use card messages for interactions.

  • Setup involves enabling APIs, configuring Pub/Sub (topic, permissions, subscriptions), and writing a script (Java, Python, or Node.js) to handle messages and send responses.

  • The architecture involves user interaction triggering a Pub/Sub message, which is then received by your application server to process and respond via the Chat API.

  • Prerequisites include a Google Workspace account, a Google Cloud Project with billing enabled, and specific language requirements (Java, Python, or Node.js).

This page explains how to create a Chat app using Pub/Sub. This type of architecture for a Chat app is useful if your organization has a firewall, which can prevent Chat from sending messages to your Chat app, or if the Chat app uses the Google Workspace Events API. However, this architecture has the following limitations due to the fact that these Chat apps can only send and receive asynchronous messages:

  • Can't use dialogs in messages. Instead, use a card message.
  • Can't update individual cards with a synchronous response. Instead, update the entire message by calling the patch method.

The following diagram shows the architecture of a Chat app built with Pub/Sub:

Architecture of a Chat app implemented with Pub/Sub.

In the preceding diagram, a user interacting with a Pub/Sub Chat app has the following flow of information:

  1. A user sends a message in Chat to a Chat app, either in a direct message or in a Chat space, or an event happens in a Chat space for which the Chat app has an active subscription.

  2. Chat sends the message to a Pub/Sub topic.

  3. An application server, that is either a cloud or on-premises system that contains the Chat app logic, subscribes to the Pub/Sub topic in order to receive the message through the firewall.

  4. Optionally, the Chat app can call the Chat API to asynchronously post messages or perform other operations.

Prerequisites

When you build your Chat app you must clear Build this Chat app as a Google Workspace add-on on the Chat API configuration page in Google Cloud console. See Publish the app to Google Chat.

Java

Python

Node.js

Set up the environment

Before using Google APIs, you need to turn them on in a Google Cloud project. You can turn on one or more APIs in a single Google Cloud project.
  • In the Google Cloud console, enable the Google Chat API and the Pub/Sub API.

    Enable the APIs

Set up Pub/Sub

  1. Create a Pub/Sub topic that the Chat API can send messages to. We recommend that you use a single topic per Chat app.

  2. Grant Chat permission to publish to the topic by assigning the Pub/Sub Publisher role to the following service account:

    chat-api-push@system.gserviceaccount.com
    
  3. Create a service account for the Chat app to authorize with Pub/Sub and Chat and save the private key file to your working directory.

  4. Create a pull subscription to the topic.

  5. Assign the Pub/Sub Subscriber Role on the subscription for the service account that you previously created.

Write the script

Java

  1. In a CLI, provide service account credentials:

    exportGOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. In a CLI, provide the Google Cloud project ID:

    exportPROJECT_ID=PROJECT_ID
    
  3. In a CLI, provide the subscription ID for the Pub/Sub subscription that you previously created:

    exportSUBSCRIPTION_ID=SUBSCRIPTION_ID
    
  4. In your working directory, create a file named pom.xml.

  5. In the pom.xml file, paste the following code:

    java/pub-sub-app/pom.xml
    <projectxmlns="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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.google.chat</groupId>
    <artifactId>pub-sub-app</artifactId>
    <version>0.1.0</version>
    <name>pub-sub-app-java</name>
    <properties>
    <maven.compiler.release>21</maven.compiler.release>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
    <!--GoogleChatGAPIClibrary-->
    <dependency>
    <groupId>com.google.api.grpc</groupId>
    <artifactId>proto-google-cloud-chat-v1</artifactId>
    <version>0.8.0</version>
    </dependency>
    <dependency>
    <groupId>com.google.api</groupId>
    <artifactId>gax</artifactId>
    <version>2.48.1</version>
    </dependency>
    <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-chat</artifactId>
    <version>0.1.0</version>
    </dependency>
    <!--GoogleCloudPub/Sublibrary-->
    <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-pubsub</artifactId>
    <version>1.125.8</version>
    </dependency>
    <!--JSONutilities-->
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
    </dependency>
    </dependencies>
    </project>
  6. In your working directory, create the directory structure src/main/java.

  7. In the src/main/java directory, create a file named Main.java.

  8. In Main.java, paste the following code:

    java/pub-sub-app/src/main/java/Main.java
    importcom.fasterxml.jackson.databind.JsonNode;
    importcom.fasterxml.jackson.databind.ObjectMapper;
    importcom.google.api.gax.core.FixedCredentialsProvider;
    importcom.google.auth.oauth2.GoogleCredentials;
    importcom.google.chat.v1.ChatServiceClient;
    importcom.google.chat.v1.ChatServiceSettings;
    importcom.google.chat.v1.CreateMessageRequest;
    importcom.google.chat.v1.CreateMessageRequest.MessageReplyOption;
    importcom.google.chat.v1.Message;
    importcom.google.chat.v1.Thread;
    importcom.google.cloud.pubsub.v1.AckReplyConsumer;
    importcom.google.cloud.pubsub.v1.MessageReceiver;
    importcom.google.cloud.pubsub.v1.Subscriber;
    importcom.google.pubsub.v1.ProjectSubscriptionName;
    importcom.google.pubsub.v1.PubsubMessage;
    importjava.io.FileInputStream;
    importjava.util.Collections;
    publicclass Main{
    publicstaticfinalStringPROJECT_ID_ENV_PROPERTY="PROJECT_ID";
    publicstaticfinalStringSUBSCRIPTION_ID_ENV_PROPERTY="SUBSCRIPTION_ID";
    publicstaticfinalStringCREDENTIALS_PATH_ENV_PROPERTY="GOOGLE_APPLICATION_CREDENTIALS";
    publicstaticvoidmain(String[]args)throwsException{
    ProjectSubscriptionNamesubscriptionName=ProjectSubscriptionName.of(
    System.getenv(Main.PROJECT_ID_ENV_PROPERTY),
    System.getenv(Main.SUBSCRIPTION_ID_ENV_PROPERTY));
    // Instantiate app, which implements an asynchronous message receiver.
    EchoAppechoApp=newEchoApp();
    // Create a subscriber for <var>SUBSCRIPTION_ID</var> bound to the message receiver
    finalSubscribersubscriber=Subscriber.newBuilder(subscriptionName,echoApp).build();
    System.out.println("Subscriber is listening to events...");
    subscriber.startAsync();
    // Wait for termination
    subscriber.awaitTerminated();
    }
    }
    /**
     * A demo app which implements {@link MessageReceiver} to receive messages. It simply echoes the
     * incoming messages.
     */
    class EchoAppimplementsMessageReceiver{
    // 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.
    privatestaticfinalStringSERVICE_ACCOUNT_KEY_PATH=
    System.getenv(Main.CREDENTIALS_PATH_ENV_PROPERTY);
    // Developer code for Google Chat API scope.
    privatestaticfinalStringGOOGLE_CHAT_API_SCOPE="https://www.googleapis.com/auth/chat.bot";
    privatestaticfinalStringADDED_RESPONSE="Thank you for adding me!";
    ChatServiceClientchatServiceClient;
    EchoApp()throwsException{
    GoogleCredentialscredential=GoogleCredentials
    .fromStream(newFileInputStream(SERVICE_ACCOUNT_KEY_PATH))
    .createScoped(Collections.singleton(GOOGLE_CHAT_API_SCOPE));
    // Create the ChatServiceSettings with the app credentials
    ChatServiceSettingschatServiceSettings=ChatServiceSettings.newBuilder()
    .setCredentialsProvider(FixedCredentialsProvider.create(credential)).build();
    // Set the Chat service client
    chatServiceClient=ChatServiceClient.create(chatServiceSettings);
    }
    // Called when a message is received by the subscriber.
    @Override
    publicvoidreceiveMessage(PubsubMessagepubsubMessage,AckReplyConsumerconsumer){
    System.out.println("Id : "+pubsubMessage.getMessageId());
    // Handle incoming message, then ack/nack the received message
    try{
    ObjectMappermapper=newObjectMapper();
    JsonNodedataJson=mapper.readTree(pubsubMessage.getData().toStringUtf8());
    System.out.println("Data : "+dataJson.toString());
    handle(dataJson);
    consumer.ack();
    }catch(Exceptione){
    System.out.println(e);
    consumer.nack();
    }
    }
    // Send message to Google Chat based on the type of event.
    publicvoidhandle(JsonNodeeventJson)throwsException{
    CreateMessageRequestcreateMessageRequest;
    switch(eventJson.get("type").asText()){
    case"ADDED_TO_SPACE":
    // 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")){
    createMessageRequest=CreateMessageRequest.newBuilder()
    .setParent(eventJson.get("space").get("name").asText())
    .setMessage(Message.newBuilder().setText(ADDED_RESPONSE).build()).build();
    break;
    }
    case"MESSAGE":
    // In case of message, post the response in the same thread.
    createMessageRequest=CreateMessageRequest.newBuilder()
    .setParent(eventJson.get("space").get("name").asText())
    .setMessageReplyOption(MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD)
    .setMessage(Message.newBuilder()
    .setText("You said: `"+eventJson.get("message").get("text").asText()+"`")
    .setThread(Thread.newBuilder()
    .setName(eventJson.get("message").get("thread").get("name").asText())
    .build()).build()).build();
    break;
    case"REMOVED_FROM_SPACE":
    default:
    // Do nothing
    return;
    }
    // Post the response to Google Chat.
    chatServiceClient.createMessage(createMessageRequest);
    }
    }

Python

  1. In a CLI, provide service account credentials:

    exportGOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. In a CLI, provide the Google Cloud project ID:

    exportPROJECT_ID=PROJECT_ID
    
  3. In a CLI, provide the subscription ID for the Pub/Sub subscription that you previously created:

    exportSUBSCRIPTION_ID=SUBSCRIPTION_ID
    
  4. In your working directory, create a file named requirements.txt.

  5. In the requirements.txt file, paste the following code:

    python/pub-sub-app/requirements.txt
    google-cloud-pubsub>=2.23.0
    google-apps-chat==0.1.9
  6. In your working directory, create a file named app.py.

  7. In app.py, paste the following code:

    python/pub-sub-app/app.py
    importjson
    importlogging
    importos
    importsys
    importtime
    fromgoogle.appsimport chat_v1 as google_chat
    fromgoogle.cloudimport pubsub_v1
    fromgoogle.oauth2.service_accountimport Credentials
    defreceive_messages():
    """Receives messages from a pull subscription."""
     scopes = ['https://www.googleapis.com/auth/chat.bot']
     service_account_key_path = os.environ.get(
     'GOOGLE_APPLICATION_CREDENTIALS')
     creds = Credentials.from_service_account_file(
     service_account_key_path)
     chat = google_chat.ChatServiceClient(
     credentials = creds,
     client_options = {
     "scopes": scopes
     })
     project_id = os.environ.get('PROJECT_ID')
     subscription_id = os.environ.get('SUBSCRIPTION_ID')
     subscriber = pubsub_v1.SubscriberClient()
     subscription_path = subscriber.subscription_path(
     project_id, subscription_id)
     # Handle incoming message, then ack/nack the received message
     defcallback(message):
     event = json.loads(message.data)
     logging.info('Data : %s', event)
     space_name = event['space']['name']
     # Post the response to Google Chat.
     request = format_request(event)
     if request is not None:
     chat.create_message(request)
     # Ack the message.
     message.ack()
     subscriber.subscribe(subscription_path, callback = callback)
     logging.info('Listening for messages on %s', subscription_path)
     # Keep main thread from exiting while waiting for messages
     while True:
     time.sleep(60)
    defformat_request(event):
    """Send message to Google Chat based on the type of event.
     Args:
     event: A dictionary with the event data.
     """
     space_name = event['space']['name']
     event_type = event['type']
     # If the app was removed, we don't respond.
     if event['type'] == 'REMOVED_FROM_SPACE':
     logging.info('App removed rom space %s', space_name)
     return
     elif event_type == 'ADDED_TO_SPACE' and 'message' not in event:
     # 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.
     return google_chat.CreateMessageRequest(
     parent = space_name,
     message = {
     'text': 'Thank you for adding me!'
     }
     )
     elif event_type in ['ADDED_TO_SPACE', 'MESSAGE']:
     # In case of message, post the response in the same thread.
     return google_chat.CreateMessageRequest(
     parent = space_name,
     message_reply_option = google_chat.CreateMessageRequest.MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD,
     message = {
     'text': 'You said: `' + event['message']['text'] + '`',
     'thread': {
     'name': event['message']['thread']['name']
     }
     }
     )
    if __name__ == '__main__':
     if 'PROJECT_ID' not in os.environ:
     logging.error('Missing PROJECT_ID env var.')
     sys.exit(1)
     if 'SUBSCRIPTION_ID' not in os.environ:
     logging.error('Missing SUBSCRIPTION_ID env var.')
     sys.exit(1)
     if 'GOOGLE_APPLICATION_CREDENTIALS' not in os.environ:
     logging.error('Missing GOOGLE_APPLICATION_CREDENTIALS env var.')
     sys.exit(1)
     logging.basicConfig(
     level=logging.INFO,
     style='{',
     format='{levelname:.1}{asctime}{filename}:{lineno}] {message}')
     receive_messages()

Node.js

  1. In a CLI, provide service account credentials:

    exportGOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH
    
  2. In a CLI, provide the Google Cloud project ID:

    exportPROJECT_ID=PROJECT_ID
    
  3. In a CLI, provide the subscription ID for the Pub/Sub subscription that you previously created:

    exportSUBSCRIPTION_ID=SUBSCRIPTION_ID
    
  4. In your working directory, create a file named package.json.

  5. In the package.json file, paste the following code:

    node/pub-sub-app/package.json
    {
    "name":"pub-sub-app",
    "version":"1.0.0",
    "description":"Google Chat App that listens for messages via Cloud Pub/Sub",
    "main":"index.js",
    "scripts":{
    "start":"node index.js",
    "test":"echo \"Error: no test specified\" && exit 1"
    },
    "dependencies":{
    "@google-apps/chat":"^0.4.0",
    "@google-cloud/pubsub":"^4.5.0"
    },
    "license":"Apache-2.0"
    }
  6. In your working directory, create a file named index.js.

  7. In index.js, paste the following code:

    node/pub-sub-app/index.js
    const{ChatServiceClient}=require('@google-apps/chat');
    const{MessageReplyOption}=require('@google-apps/chat').protos.google.chat.v1.CreateMessageRequest;
    const{PubSub}=require('@google-cloud/pubsub');
    const{SubscriberClient}=require('@google-cloud/pubsub/build/src/v1');
    // Receives messages from a pull subscription.
    functionreceiveMessages(){
    constchat=newChatServiceClient({
    keyFile:process.env.GOOGLE_APPLICATION_CREDENTIALS,
    scopes:['https://www.googleapis.com/auth/chat.bot'],
    });
    constsubscriptionPath=newSubscriberClient()
    .subscriptionPath(process.env.PROJECT_ID,process.env.SUBSCRIPTION_ID)
    constsubscription=newPubSub()
    .subscription(subscriptionPath);
    // Handle incoming message, then ack/nack the received message
    constmessageHandler=message=>{
    console.log(`Id : ${message.id}`);
    constevent=JSON.parse(message.data);
    console.log(`Data : ${JSON.stringify(event)}`);
    // Post the response to Google Chat.
    constrequest=formatRequest(event);
    if(request!=null){
    chat.createMessage(request);
    }
    // Ack the message.
    message.ack();
    }
    subscription.on('message',messageHandler);
    console.log(`Listening for messages on ${subscriptionPath}`);
    // Keep main thread from exiting while waiting for messages
    setTimeout(()=>{
    subscription.removeListener('message',messageHandler);
    console.log(`Stopped listening for messages.`);
    },60*1000);
    }
    // Send message to Google Chat based on the type of event
    functionformatRequest(event){
    constspaceName=event.space.name;
    consteventType=event.type;
    // If the app was removed, we don't respond.
    if(event.type=='REMOVED_FROM_SPACE'){
    console.log(`App removed rom space ${spaceName}`);
    returnnull;
    }elseif(eventType=='ADDED_TO_SPACE' && !eventType.message){
    // 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.
    return{
    parent:spaceName,
    message:{text:'Thank you for adding me!'}
    };
    }elseif(eventType=='ADDED_TO_SPACE'||eventType=='MESSAGE'){
    // In case of message, post the response in the same thread.
    return{
    parent:spaceName,
    messageReplyOption:MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD,
    message:{
    text:'You said: `'+event.message.text+'`',
    thread:{name:event.message.thread.name}
    }
    };
    }
    }
    if(!process.env.PROJECT_ID){
    console.log('Missing PROJECT_ID env var.');
    process.exit(1);
    }
    if(!process.env.SUBSCRIPTION_ID){
    console.log('Missing SUBSCRIPTION_ID env var.');
    process.exit(1);
    }
    if(!process.env.GOOGLE_APPLICATION_CREDENTIALS){
    console.log('Missing GOOGLE_APPLICATION_CREDENTIALS env var.');
    process.exit(1);
    }
    receiveMessages();

Publish the app to Chat

  1. In the Google Cloud console, go to Menu > APIs & Services > Enabled APIs & Services > Google Chat API > Configuration.

    Go to Configuration

  2. Configure the Chat app for Pub/Sub:

    1. Clear Build this Chat app as a Google Workspace add-on. A dialog opens asking you to confirm. In the dialog, click Disable.
    2. In App name, enter Quickstart App.
    3. In Avatar URL, enter https://developers.google.com/chat/images/quickstart-app-avatar.png.
    4. In Description, enter Quickstart app.
    5. Under Functionality, select Join spaces and group conversations.
    6. Under Connection settings, select Cloud Pub/Sub and paste the name of the Pub/Sub topic that you previously created.
    7. Under Visibility, select Make this Google Chat app available to specific people and groups in your domain and enter your email address.
    8. Under Logs, select Log errors to Logging.
  3. Click Save.

The app is ready to receive and respond to messages on Chat.

Run the script

In a CLI, switch into your working directory and run the script:

Java

mvncompileexec:java-Dexec.mainClass=Main

Python

python-mvenvenv
sourceenv/bin/activate
pipinstall-rrequirements.txt-U
pythonapp.py

Node.js

npminstall
npmstart

When you run the code, the application starts listening to messages published to the Pub/Sub topic.

Test your Chat app

To test your Chat app, open a direct message space with the Chat app and send a message:

  1. Open Google Chat using the Google Workspace account that you provided when you added yourself as a trusted tester.

    Go to Google Chat

  2. Click New chat.
  3. In the Add 1 or more people field, type the name of your Chat app.
  4. Select your Chat app from the results. A direct message opens.

  5. In the new direct message with the app, type Hello and press enter.

To add trusted testers and learn more about testing interactive features, see Test interactive features for Google Chat apps.

Troubleshoot

When a Google Chat app or card returns an error, the Chat interface surfaces a message saying "Something went wrong." or "Unable to process your request." Sometimes the Chat UI doesn't display any error message, but the Chat app or card produces an unexpected result; for example, a card message might not appear.

Although an error message might not display in the Chat UI, descriptive error messages and log data are available to help you fix errors when error logging for Chat apps is turned on. For help viewing, debugging, and fixing errors, see Troubleshoot and fix Google Chat errors.

Clean up

To avoid incurring charges to your Google Cloud account for the resources used in this tutorial, we recommend that you delete the Cloud project.

  1. In the Google Cloud console, go to the Manage resources page. Click Menu > IAM & Admin > Manage Resources.

    Go to Resource Manager

  2. In the project list, select the project you want to delete and then click Delete .
  3. In the dialog, type the project ID and then click Shut down to delete the project.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025年10月13日 UTC.