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.
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.
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.
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:
- 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.
- 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.
GCP Setup
Six steps to configure Google Cloud. Work through them in order.
Enable APIs
In the GCP API Library, search for and enable both:
- Cloud Pub/Sub API —
pubsub.googleapis.com - Google Workspace Events API —
workspaceevents.googleapis.com
Also ensure the Google Chat API is enabled (usually already on for Workspace accounts).
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-subautomatically - 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
chat-api-push@system.gserviceaccount.com, select role "Pub/Sub Publisher", save.
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.
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.
http://localhost:3000/oauth/callback. Note the Client ID and Secret from the confirmation dialog.
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 (supersedeschat.messages.readonly)https://www.googleapis.com/auth/chat.spaces.readonly— create Workspace Events subscriptions on spaceshttps://www.googleapis.com/auth/chat.messages.reactions— add/remove emoji reactionshttps://www.googleapis.com/auth/pubsub— pull messages from Pub/Sub subscription
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:
https://chat.google.com/room/AAQA6hUKWfM
↓
spaces/AAQA6hUKWfM
From API: List your spaces to find the name:
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.
Plugin Installation
Copy the plugin files into OpenClaw's extensions directory.
Clone the Repository
# Clone the plugin repo directly into the extensions directory
git clone https://github.com/teyou/openclaw-googlechatpubsub-plugin.git \
~/.openclaw/extensions/googlechatpubsub
Verify Files
ls ~/.openclaw/extensions/googlechatpubsub/
# Should output:
# CONTRIBUTING.md LICENSE README.md index.ts openclaw.plugin.json
The repo includes the full plugin manifest (
openclaw.plugin.json), implementation (index.ts), setup instructions, and contribution guidelines.
extensions/ directory on startup. Once files are in place and the plugin is added to plugins.allow in config, it will be loaded automatically.
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.
{
"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"
}
]
}
]
}
}
}
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.
{
"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 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:
"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 }
]
}
]
OAuth Authorization Flow
One-time setup. After the initial token exchange, the plugin auto-refreshes.
Build the Authorization URL
Construct the URL manually or use the plugin's built-in helper command (if available). The URL format is:
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.
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.
Copy the Authorization Code
After consent, Google redirects to http://localhost:3000/oauth/callback?code=XXXXXXXX. Copy the code query parameter value.
Exchange Code for Tokens
POST the code to Google's token endpoint:
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.
Save Tokens to File
Save the full JSON response to the path configured in tokensFile:
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'
Restart & Verify
Restart the OpenClaw gateway. The plugin will load the tokens, verify them, and start polling Pub/Sub.
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.
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:
[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.
[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:
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:
| Principal | Resource | Role | Purpose |
|---|---|---|---|
| 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 |
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 spacementionKeywordmatch — 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.
↓
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
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.
Every message → same context window
threadSessionIsolation: true ✓
Each thread has its own session. Agents only see that thread's history.
Each thread → isolated context
replyInThread: true + threadSessionIsolation: true gives you clean threaded conversations with fully isolated context per topic — ideal for multi-agent team spaces.
Troubleshooting
Common issues and how to resolve them.
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.
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.
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.
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.
Run openclaw plugins list and check if googlechatpubsub appears.
Fix checklist:
- Confirm
plugins.allowinopenclaw.jsonincludes"googlechatpubsub" - Confirm
~/.openclaw/extensions/googlechatpubsub/contains bothindex.tsandopenclaw.plugin.json - Confirm
channels.googlechatpubsub.enabledistrue - Check gateway logs for TypeScript compilation errors in the plugin
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.
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.
Configuration Reference
All available config options for channels.googlechatpubsub in openclaw.json.
Channel Config
| Key | Type | Default | Req? | 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[])
| Key | Type | Default | Req? | 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[])
| Key | Type | Default | Req? | 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. |
alwaysListen or mentionKeyword should be set per agent.
An agent with neither will never receive messages.