/ googlechatpubsub ⚡ Setup Guide
📦 Channel Plugin

googlechatpubsub
Setup & Installation Guide

Connect OpenClaw agents to Google Chat spaces via Pub/Sub — no @mention required. Full multi-agent routing, thread isolation, file attachment support, and auto-renewing subscriptions.

📌 Version 0.3.0-alpha 🔧 Plugin type channel ☁️ Requires GCP + Workspace
1

Overview

What it does, how it works, and what you need before you start.

What the Plugin Does

The googlechatpubsub plugin enables OpenClaw agents to listen to any Google Chat space continuously — without requiring @mentions. Messages are delivered via Google Cloud Pub/Sub in near-real-time, then routed to one or more configured agents based on keyword matching or always-on listeners.

Unlike the standard Chat REST API (which requires polling or webhooks), this plugin leverages the Google Workspace Events API to subscribe to space events and push them to a Pub/Sub topic. This gives you low-latency, reliable message delivery with automatic subscription renewal.

How It Works

When a message is sent in a monitored Chat space, Google's Workspace Events API fires an event that lands in your Pub/Sub topic. The plugin polls that subscription, decodes the event, and passes the message through OpenClaw's pipeline. Agents formulate a reply, which is sent back via the Google Chat REST API — all in under a second on a healthy connection.

Google Chat Space message event Workspace Events API subscription push Cloud Pub/Sub topic + sub poll Plugin decode + route msg OpenClaw agent pipeline reply via Chat API

Prerequisites

OpenClaw Gateway

Running locally or on a server. Check with openclaw gateway status.

Google Workspace Account

With admin access to configure OAuth consent and Workspace Events API.

Google Cloud Project

Billing enabled. You'll create a Pub/Sub topic and OAuth credentials here.

Node.js ≥ 18

Required to run the plugin. OpenClaw ships with a compatible runtime.

Google Chat Space

An existing space where your bot account is a member.

Agent(s) Configured

At least one OpenClaw agent defined in openclaw.json to receive messages.

⚠️
OAuth User Must Be a Member of Every Monitored Space

The Workspace Events API creates subscriptions scoped to the OAuth user's access. If that user isn't in the space, subscriptions.create will fail — the API can't subscribe to events in a space the user can't see. The chat.messages.readonly scope only grants access to messages in spaces the user belongs to.

Two separate auth paths:

  • Listening (Workspace Events subscription) → OAuth user token → user must be a space member
  • Replying (Chat API send) → Bot service account → app must be installed in the space

Both are required — the bot app being installed in the space is not enough for listening. The OAuth user must also be present.

Workarounds if you can't add the OAuth user:

  1. Domain-wide delegation (DWD) — service account impersonates a user who IS in the space. Also extends subscription TTL from 4h to 24h. Requires Workspace admin configuration.
  2. App auth (Developer Preview) — Google is working on letting service accounts create subscriptions directly. Not stable yet.

Recommendation: Use the same Google Workspace admin account that manages the Chat app as the OAuth user. Since they typically create/manage spaces anyway, access isn't a blocker.

2

GCP Setup

Six steps to configure Google Cloud. Work through them in order.

1

Enable APIs

In the GCP API Library, search for and enable both:

  • Cloud Pub/Sub APIpubsub.googleapis.com
  • Google Workspace Events APIworkspaceevents.googleapis.com

Also ensure the Google Chat API is enabled (usually already on for Workspace accounts).

🖼️
GCP Console → APIs & Services → Library Search "Pub/Sub" → Enable. Then search "Workspace Events" → Enable. Both should show a green checkmark when active.
2

Create Pub/Sub Topic

Go to Pub/Sub → Topics → Create Topic. Use a descriptive name like openclaw-gchat-events.

  • Check "Add a default subscription" — this creates openclaw-gchat-events-sub automatically
  • Leave other settings at defaults and click Create

Add IAM binding: After creating the topic, go to its Permissions tab and add:

  • Principal: chat-api-push@system.gserviceaccount.com
  • Role: Pub/Sub Publisher
⚠️
This IAM binding is critical. Without it, Google Chat cannot push events to your topic. The subscription will exist but receive no messages.
🖼️
Pub/Sub → Topics → [your-topic] → Permissions → Add Principal Enter chat-api-push@system.gserviceaccount.com, select role "Pub/Sub Publisher", save.
3

OAuth Consent Screen

Go to APIs & Services → OAuth consent screen.

  • User type: Internal (restricts to your Workspace org — recommended)
  • App name: something like OpenClaw Chat Integration
  • Support email: your admin email
  • Add your domain to Authorized domains

You do not need to submit for verification for Internal apps.

🖼️
APIs & Services → OAuth consent screen → Create Select "Internal", fill in app name and contact email, click Save and Continue.
4

Create OAuth Client

Go to APIs & Services → Credentials → Create Credentials → OAuth client ID.

  • Application type: Web application
  • Name: OpenClaw Local Dev
  • Authorized redirect URIs: http://localhost:3000/oauth/callback

After creating, download the credentials JSON or copy the Client ID and Client Secret — you'll need both in your config.

🖼️
Credentials → Create → OAuth client ID → Web application Add redirect URI: http://localhost:3000/oauth/callback. Note the Client ID and Secret from the confirmation dialog.
5

Add OAuth Scopes

Back on the OAuth consent screen, go to Scopes and add:

  • https://www.googleapis.com/auth/chat.messages — read messages + send replies (supersedes chat.messages.readonly)
  • https://www.googleapis.com/auth/chat.spaces.readonly — create Workspace Events subscriptions on spaces
  • https://www.googleapis.com/auth/chat.messages.reactions — add/remove emoji reactions
  • https://www.googleapis.com/auth/pubsub — pull messages from Pub/Sub subscription
ℹ️
Scopes update immediately for Internal apps. You may need to re-authorize if you add scopes after the initial token exchange.
6

Get the Space ID

The plugin needs the Google Chat space name in the format spaces/XXXXXXXXXXX.

From URL: Open the space in a browser. The URL looks like:

text
https://chat.google.com/room/AAQA6hUKWfM
                                      ↓
                              spaces/AAQA6hUKWfM

From API: List your spaces to find the name:

bash
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://chat.googleapis.com/v1/spaces" | jq '.spaces[] | {name, displayName}'

The name field is what you use in config — e.g., spaces/AAQA6hUKWfM.

3

Plugin Installation

Copy the plugin files into OpenClaw's extensions directory.

Clone the Repository

bash
# Clone the plugin repo directly into the extensions directory
git clone https://github.com/teyou/openclaw-googlechatpubsub-plugin.git \
  ~/.openclaw/extensions/googlechatpubsub

Verify Files

bash
ls ~/.openclaw/extensions/googlechatpubsub/
# Should output:
# CONTRIBUTING.md  LICENSE  README.md  index.ts  openclaw.plugin.json
ℹ️
Source & docs: github.com/teyou/openclaw-googlechatpubsub-plugin
The repo includes the full plugin manifest (openclaw.plugin.json), implementation (index.ts), setup instructions, and contribution guidelines.
Plugin discovered automatically. OpenClaw scans the extensions/ directory on startup. Once files are in place and the plugin is added to plugins.allow in config, it will be loaded automatically.
4

Configuration

Add the channel config and agent bindings to your openclaw.json.

Channel Config v0.2.0+

Add the following to your openclaw.json. Since v0.2.0 the plugin reads config from channels.googlechatpubsub (standard channel convention). The legacy plugins.entries path still works as a fallback.

jsonc
{
  "plugins": {
    "allow": ["googlechatpubsub"]
  },

  "channels": {
    "googlechatpubsub": {
      "enabled": true,
      "projectId":        "my-gcp-project-id",
      "topicId":          "openclaw-gchat-events",
      "subscriptionId":   "openclaw-gchat-events-sub",
      "pollIntervalSeconds": 3,
      "renewalBufferMinutes": 30,
      "oauth": {
        "clientId":     "123456789-abc.apps.googleusercontent.com",
        "clientSecret": "GOCSPX-your-client-secret",
        "tokensFile":   "~/.openclaw/gchat-tokens.json"
      },
      "bindings": [
        {
          "space": "spaces/ABC123example",
          "replyInThread": true,
          "threadSessionIsolation": true,
          "agents": [
            {
              "agentId": "chief-of-staff",
              "alwaysListen": true
            },
            {
              "agentId": "engineer",
              "mentionKeyword": "eng"
            },
            {
              "agentId": "designer",
              "mentionKeyword": "design"
            }
          ]
        }
      ]
    }
  }
}
Migrating from v0.1.x? Move your config from plugins.entries.googlechatpubsub.config.{...} up to channels.googlechatpubsub.{...}. The old path still works as a fallback — no rush.

Root-Level Bindings

To route incoming Pub/Sub messages to the correct OpenClaw agent sessions, add a bindings array at the root of openclaw.json. Each binding maps an accountId (which matches the agentId in the channel config) to a specific agent.

json
{
  "bindings": [
    {
      "agentId": "chief-of-staff",
      "match": {
        "channel": "googlechatpubsub",
        "accountId": "chief-of-staff"
      }
    },
    {
      "agentId": "engineer",
      "match": {
        "channel": "googlechatpubsub",
        "accountId": "engineer"
      }
    },
    {
      "agentId": "designer",
      "match": {
        "channel": "googlechatpubsub",
        "accountId": "designer"
      }
    }
  ]
}
⚠️
agentId must match in both places. The agentId in the plugin's bindings[].agents[] must match the accountId in the root bindings[].match block — and both must correspond to a real agent key in your agents config.

Multiple Spaces

To monitor more than one space, add additional entries to the bindings array inside the plugin config:

json
"bindings": [
  {
    "space": "spaces/ABC123example",
    "replyInThread": true,
    "threadSessionIsolation": true,
    "agents": [
      { "agentId": "chief-of-staff", "alwaysListen": true }
    ]
  },
  {
    "space": "spaces/XYZ789another",
    "replyInThread": false,
    "agents": [
      { "agentId": "engineer", "alwaysListen": true }
    ]
  }
]
5

OAuth Authorization Flow

One-time setup. After the initial token exchange, the plugin auto-refreshes.

ℹ️
This is a one-time manual step. After you complete the flow below and save your tokens, the plugin handles all future token refreshes automatically. You only need to repeat this if the refresh token is revoked (e.g., user revokes app access from Google Account settings).
1

Build the Authorization URL

Construct the URL manually or use the plugin's built-in helper command (if available). The URL format is:

text
https://accounts.google.com/o/oauth2/v2/auth
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=http://localhost:3000/oauth/callback
  &response_type=code
  &scope=https://www.googleapis.com/auth/chat.messages
         https://www.googleapis.com/auth/chat.spaces.readonly
         https://www.googleapis.com/auth/chat.messages.reactions
         https://www.googleapis.com/auth/pubsub
  &access_type=offline
  &prompt=consent

Replace YOUR_CLIENT_ID with your OAuth client ID. The access_type=offline parameter is essential — it ensures you receive a refresh_token.

2

Open URL & Grant Consent

Open the URL in a browser logged into your Workspace account. Google will show the OAuth consent screen listing the requested scopes. Click Allow.

🔐
Google OAuth consent screen Shows app name, requested scopes (read messages, spaces, reactions), and Allow/Deny buttons.
3

Copy the Authorization Code

After consent, Google redirects to http://localhost:3000/oauth/callback?code=XXXXXXXX. Copy the code query parameter value.

⏱️
Codes expire in ~60 seconds. Proceed to the next step immediately.
4

Exchange Code for Tokens

POST the code to Google's token endpoint:

bash
curl -X POST https://oauth2.googleapis.com/token \
  -d "code=AUTHORIZATION_CODE_HERE" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=http://localhost:3000/oauth/callback" \
  -d "grant_type=authorization_code"

You'll receive a JSON response with access_token, refresh_token, and expires_in.

5

Save Tokens to File

Save the full JSON response to the path configured in tokensFile:

bash
curl -X POST https://oauth2.googleapis.com/token \
  -d "code=AUTHORIZATION_CODE_HERE" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=http://localhost:3000/oauth/callback" \
  -d "grant_type=authorization_code" \
  | jq '.' > ~/.openclaw/gchat-tokens.json

# Verify it contains a refresh_token:
cat ~/.openclaw/gchat-tokens.json | jq '.refresh_token'
6

Restart & Verify

Restart the OpenClaw gateway. The plugin will load the tokens, verify them, and start polling Pub/Sub.

bash
openclaw gateway restart

# Check plugin loaded successfully:
openclaw plugins list

You should see googlechatpubsub listed as active. Token auto-refresh runs in the background — you won't need to repeat this flow unless the refresh token is revoked.

6

Workspace Events Subscription

How the plugin creates and maintains Google Chat event subscriptions.

Auto-Create on Startup

When the plugin starts, it calls the Workspace Events API to create a subscription for each configured space. Subscriptions deliver google.workspace.chat.message.v1.created events directly to your Pub/Sub topic.

You don't need to create subscriptions manually — the plugin handles this automatically. Check the OpenClaw logs on startup to see subscription creation:

text
[googlechatpubsub] Creating subscription for spaces/ABC123example...
[googlechatpubsub] Subscription created: subscriptions/abc-123-def-456
[googlechatpubsub] Polling started (interval: 2000ms)

4-Hour TTL & Auto-Renewal

Workspace Events subscriptions have a maximum TTL of 4 hours. The plugin tracks expiry times and automatically calls the UpdateSubscription API before expiry to extend the TTL. This is invisible to you during normal operation.

If renewal fails (e.g., network issue), the plugin will log a warning and attempt to re-create the subscription on the next poll cycle.

text
[googlechatpubsub] Renewing subscription expiring in 5 minutes...
[googlechatpubsub] Subscription renewed until 2024-01-15T14:30:00Z

Manually Check Subscriptions

To list active subscriptions for your spaces:

bash
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://workspaceevents.googleapis.com/v1/subscriptions" | jq '.'

Required IAM Permissions

Recap of the IAM bindings needed for subscriptions to work end-to-end:

PrincipalResourceRolePurpose
chat-api-push@system.gserviceaccount.com Pub/Sub Topic roles/pubsub.publisher Allows Chat to push events to your topic
Your OAuth user account GCP Project roles/pubsub.subscriber Allows the plugin to pull from the subscription
7

Multi-Agent Routing

How incoming messages are matched and dispatched to agents.

Routing Logic

For each incoming message, the plugin evaluates all configured agents for that space. An agent receives the message if either:

  • alwaysListen: true — always receives every message from the space
  • mentionKeyword match — the keyword appears anywhere in the message text (case-insensitive)

Multiple agents can be dispatched for a single message. Each receives the full message and replies independently.

Message: "hey eng, can you review this PR?"
  
  evaluate agents for spaces/ABC123example:
     chief-of-staff   alwaysListen: true   ✅ included
     engineer        mentionKeyword: "eng"   → "eng" found   ✅ included
     designer        mentionKeyword: "design" → not found   ⛔ skipped
  
  Dispatch to: [chief-of-staff, engineer]

Routing Examples

"hello everyone, status update?"
chief-of-staff only (alwaysListen, no other keywords matched)
"design a new onboarding screen"
chief-of-staff + designer ("design" keyword matched)
"eng and design, please sync up on the spec"
chief-of-staff + engineer + designer (both keywords matched)
"can eng ship the fix by EOD?"
chief-of-staff + engineer ("eng" keyword matched)
💡
Keyword matching is substring-based. A message containing "engineering" will match the keyword "eng". Use more specific keywords to avoid unintended matches (e.g., use "design:" or "@design" as the keyword).
8

Thread Behavior

Control how agents reply and how conversation context is scoped.

replyInThread

When replyInThread: true, the plugin always sends replies as thread replies to the original message — never to the top-level space. This keeps spaces clean and conversations organized.

When false (default), replies are sent to the space's main message window.

threadSessionIsolation

Controls how conversation context (session key) is scoped for multi-turn conversations. This determines what history an agent "remembers" within a space.

threadSessionIsolation: false

All messages in the space share one session. Agents see full space history.

session: googlechatpubsub:spaces/ABC123
session: googlechatpubsub:spaces/ABC123
session: googlechatpubsub:spaces/ABC123

Every message → same context window

threadSessionIsolation: true ✓

Each thread has its own session. Agents only see that thread's history.

session: googlechatpubsub:spaces/ABC123:thread-1
session: googlechatpubsub:spaces/ABC123:thread-2
session: googlechatpubsub:spaces/ABC123:thread-3

Each thread → isolated context

Recommended: enable both. replyInThread: true + threadSessionIsolation: true gives you clean threaded conversations with fully isolated context per topic — ideal for multi-agent team spaces.
9

Troubleshooting

Common issues and how to resolve them.

🔑 OAuth token expired or invalid

The plugin auto-refreshes access tokens using the refresh_token. If you see 401 errors, the refresh token may have been revoked.

Fix: Re-run the OAuth authorization flow (Section 5) and overwrite gchat-tokens.json with fresh tokens. This happens if a user revokes app access in Google Account settings, or if tokens haven't been used for 6 months.

Subscription expired — messages not arriving

Workspace Events subscriptions expire after 4 hours. The plugin renews them automatically, but renewal can fail if the gateway was offline.

Fix: Restart the OpenClaw gateway. On startup, the plugin checks subscription state and creates a new one if needed. Look for [googlechatpubsub] Creating subscription... in logs.

⚠️ Subscription creation fails with 403 or 404

The OAuth user is not a member of the target Chat space. The Workspace Events API scopes subscriptions to the OAuth user's access — if they can't see the space, subscriptions.create will return a 403 or 404.

Fix: Add the OAuth user account to the space (as a member or manager) and retry. Confirm the space ID in your bindings config matches the space the user was added to.

📭 Messages not arriving in Pub/Sub

Check two things in order:

1. IAM binding: Verify chat-api-push@system.gserviceaccount.com has Pub/Sub Publisher on your topic. This is the most common cause.

2. Subscription exists: Go to GCP → Pub/Sub → Subscriptions and verify your subscription is listed and the topic matches.

3. Bot is a space member: The account used for OAuth must be a member of the monitored space.

🔌 Plugin not starting / not in plugins list

Run openclaw plugins list and check if googlechatpubsub appears.

Fix checklist:

  • Confirm plugins.allow in openclaw.json includes "googlechatpubsub"
  • Confirm ~/.openclaw/extensions/googlechatpubsub/ contains both index.ts and openclaw.plugin.json
  • Confirm channels.googlechatpubsub.enabled is true
  • Check gateway logs for TypeScript compilation errors in the plugin
🚫 403 error when adding reactions

The OAuth token is missing the chat.messages.reactions scope.

Fix: Add the scope to the OAuth consent screen (GCP → APIs & Services → OAuth consent screen → Scopes), then re-run the OAuth flow to get a new token with the updated scopes.

🤖 Agent not responding to messages

Verify root-level bindings in openclaw.json are configured correctly (Section 4). The accountId in match must exactly match the agentId in the plugin's bindings[].agents[] array.

Also confirm the agentId refers to a real, enabled agent in your agents config block.

10

Configuration Reference

All available config options for channels.googlechatpubsub in openclaw.json.

Channel Config

KeyTypeDefaultReq?Description
enabled boolean true Optional Enable or disable the plugin
projectId string Required GCP project ID (e.g. my-project-123)
topicId string Required Pub/Sub topic short name (e.g. openclaw-gchat-events). Full path is constructed automatically.
subscriptionId string Required Pub/Sub subscription short name (e.g. openclaw-gchat-events-sub)
bindings array Required Space-to-agent routing rules. See binding schema below.
oauth.clientId string Required OAuth 2.0 client ID from GCP Credentials
oauth.clientSecret string Required OAuth 2.0 client secret from GCP Credentials
oauth.tokensFile string Required Path to read/write OAuth tokens JSON file
pollIntervalSeconds number 3 Optional Seconds between Pub/Sub polls. Lower = lower latency, higher API usage.
renewalBufferMinutes number 30 Optional Minutes before subscription expiry to trigger auto-renewal.
agentTimeoutSeconds number 60 Optional Timeout for agent pipeline execution per message.
serviceAccountFile string from googlechat Optional Path to bot service account JSON. Falls back to channels.googlechat.serviceAccountFile.

Binding Object (bindings[])

KeyTypeDefaultReq?Description
space string Required Google Chat space name in spaces/XXXXXXXXXXX format
agents array Required List of agent routing rules for this space. See agent schema below.
replyInThread boolean false Optional When true, all replies are sent as thread replies to the triggering message.
threadSessionIsolation boolean false Optional When true, each thread gets its own session key — agents only see context from that thread.

Agent Routing Object (bindings[].agents[])

KeyTypeDefaultReq?Description
agentId string Required Must match the accountId in root-level bindings.
alwaysListen boolean false Optional When true, agent receives every message from this space regardless of content.
mentionKeyword string Optional Case-insensitive substring match. Agent is dispatched when this keyword appears in the message text.
ℹ️
At least one of alwaysListen or mentionKeyword should be set per agent. An agent with neither will never receive messages.