📖 Visual Setup Guide — step-by-step installation with screenshots
Listen to Google Chat spaces via Workspace Events API + Cloud Pub/Sub — no @mention required.
Messages arrive through Pub/Sub, get routed to one or more agents by keyword or always-listen rules, processed through the OpenClaw pipeline, and replied via the Google Chat API. Thread-first replies and per-thread session isolation are supported.
Status: Alpha (v0.2.0). Works in production but APIs may change.
Google Chat Space
│
▼
Workspace Events API ──► Cloud Pub/Sub Topic
│
▼
┌─────────────────────┐
│ googlechatpubsub │
│ (OpenClaw plugin) │
│ │
│ Poll ► Filter ► │
│ Route ► Pipeline │
└────────┬────────────┘
│
▼
Google Chat API (reply)
- No @mention required — Pub/Sub delivers ALL messages from subscribed spaces
- Multi-agent routing — keyword matching + always-listen rules
- Thread-first replies — bot replies in threads, never clutters main window
- Thread-scoped sessions — each thread gets its own conversation context
- File attachment support — downloads user-uploaded files (images, PDFs, docs) via Chat API and passes them to agents
- Emoji reactions — 👀 on received messages via Chat Reactions API
- Auto-renewing subscriptions — handles the 4-hour Workspace Events TTL automatically
- In-process pipeline — uses OpenClaw SDK directly (no subprocess hacks)
- OpenClaw gateway running (v0.23+)
- Google Workspace account (not consumer Gmail)
- GCP project with billing enabled
- A Google Chat app (bot) already configured with a service account
- The OAuth user must be a member of every space in the bindings config
openclaw plugins install @teyou/openclaw-googlechatpubsub# Create the plugin directory
mkdir -p ~/.openclaw/extensions/googlechatpubsub
# Copy plugin files
cp openclaw.plugin.json ~/.openclaw/extensions/googlechatpubsub/
cp index.ts ~/.openclaw/extensions/googlechatpubsub/In the GCP Console:
- Cloud Pub/Sub API — Enable
- Google Workspace Events API — Enable
# Via GCP Console or gcloud:
gcloud pubsub topics create openclaw-chat-events
gcloud pubsub subscriptions create openclaw-chat-events-sub \
--topic=openclaw-chat-eventsGrant the Chat API push service account permission to publish:
gcloud pubsub topics add-iam-policy-binding openclaw-chat-events \
--member="serviceAccount:chat-api-push@system.gserviceaccount.com" \
--role="roles/pubsub.publisher"- Go to APIs & Services → OAuth consent screen
- Select Internal audience
- Set app name (e.g., "OpenClaw Pub/Sub")
- Add developer contact email
- Add scopes:
https://www.googleapis.com/auth/chat.messages(read + send; supersedeschat.messages.readonly)https://www.googleapis.com/auth/chat.spaces.readonlyhttps://www.googleapis.com/auth/chat.messages.reactionshttps://www.googleapis.com/auth/pubsub
- Go to APIs & Services → Credentials → Create Credentials → OAuth client ID
- Type: Web application
- Add redirect URI:
http://localhost:3000/oauth/callback - Download the JSON credentials file
Generate the authorization URL:
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
Open the URL, grant consent, copy the code parameter from the redirect, then exchange it:
curl -s -X POST https://oauth2.googleapis.com/token \
-d "code=AUTH_CODE" \
-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" | python3 -m json.tool > ~/gchat-tokens.jsonFrom the Google Chat URL:
https://chat.google.com/room/ABC123example→spaces/ABC123examplehttps://mail.google.com/mail/u/0/#chat/space/ABC123example→spaces/ABC123example
Add to your openclaw.json:
Migrating from v0.1.x? The old
plugins.entries.googlechatpubsub.configpath still works as a fallback. Move your config tochannels.googlechatpubsubwhen convenient — no other changes needed.
Restart the gateway:
openclaw gateway restartVerify the plugin loaded:
openclaw plugins listMessages are routed based on two rules:
alwaysListenagents receive every messagementionKeywordagents receive messages containing their keyword (case-insensitive)
Both are combined — alwaysListen agents are always included, keyword-matched agents are added on top (deduped).
| Message | Routes to |
|---|---|
"hello everyone" |
chief-of-staff |
"eng, review this PR" |
chief-of-staff + engineer |
"design a new landing page" |
chief-of-staff + designer |
"eng and design, sync up" |
chief-of-staff + engineer + designer |
| Setting | Effect |
|---|---|
replyInThread: false |
Bot replies in main window (default) |
replyInThread: true |
Bot always replies in a thread |
threadSessionIsolation: true |
Each thread gets its own session/memory |
threadSessionIsolation: false |
All threads share one space-level session |
When replyInThread is enabled and threadSessionIsolation is not set, it defaults to true.
- Workspace Events subscriptions have a 4-hour TTL
- The plugin auto-creates subscriptions on startup
- Checks and renews every 5 minutes (configurable via
renewalBufferMinutes) - State persisted in
~/.openclaw/gchat-pubsub-subscription-state.json
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Enable/disable the plugin |
projectId |
string | required | GCP project ID |
topicId |
string | required | Pub/Sub topic ID |
subscriptionId |
string | required | Pub/Sub subscription ID |
pollIntervalSeconds |
number | 3 |
Seconds between Pub/Sub polls |
renewalBufferMinutes |
number | 30 |
Minutes before expiry to renew subscription |
agentTimeoutSeconds |
number | 60 |
Timeout for agent pipeline execution |
serviceAccountFile |
string | from googlechat | Path to bot service account JSON |
oauth.clientId |
string | required | OAuth 2.0 client ID |
oauth.clientSecret |
string | required | OAuth 2.0 client secret |
oauth.redirectUri |
string | — | OAuth redirect URI |
oauth.tokensFile |
string | required | Path to OAuth tokens JSON file |
bindings[].space |
string | required | Google Chat space name |
bindings[].replyInThread |
boolean | false |
Always reply in threads |
bindings[].threadSessionIsolation |
boolean | =replyInThread | Isolate sessions per thread |
bindings[].agents[].agentId |
string | required | Agent ID to route to |
bindings[].agents[].mentionKeyword |
string | — | Keyword trigger for this agent |
bindings[].agents[].alwaysListen |
boolean | false |
Receive all messages |
| Issue | Fix |
|---|---|
Plugin not in openclaw plugins list |
Add "googlechatpubsub" to plugins.allow array |
| Subscription fails with 403/404 | OAuth user must be a member of the target space |
| Messages not arriving | Verify IAM binding on Pub/Sub topic for chat-api-push@system.gserviceaccount.com |
| OAuth token expired | Plugin auto-refreshes; if refresh_token revoked, re-run the auth flow |
| 403 on reactions | Missing chat.messages.reactions scope; re-auth with all scopes |
| Multiple replies per message | Check for duplicate listener processes; only one should run |
See CONTRIBUTING.md for development setup, code style, and how to submit PRs.
MIT
- 📖 Setup Guide — visual step-by-step installation guide
- OpenClaw Docs
- Google Workspace Events API
- Cloud Pub/Sub Documentation
{ // Register the plugin "plugins": { "allow": ["googlechatpubsub"] }, // Channel config (standard convention since v0.2.0) "channels": { "googlechatpubsub": { "enabled": true, "projectId": "your-gcp-project", "topicId": "openclaw-chat-events", "subscriptionId": "openclaw-chat-events-sub", "pollIntervalSeconds": 3, "renewalBufferMinutes": 30, "oauth": { "clientId": "YOUR_OAUTH_CLIENT_ID", "clientSecret": "YOUR_OAUTH_CLIENT_SECRET", "tokensFile": "/home/you/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" } ] } ] } }, // Route each agent to its own session "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" } } ] }