Run mibyan gateway setup and pick Google Chat for a guided walk-through.
Workspace editionGoogle Chat is part of Google Workspace. You can use this integration with a
personal Workspace (
@yourdomain.com registered through Google) or a work
Workspace where you have the Admin rights to publish an app. Gmail-only accounts
cannot host Chat apps.Overview
Step 1: Create or pick a GCP project
You need a Google Cloud project to host the Pub/Sub topic. If you don’t have one, create it at console.cloud.google.com — personal accounts get a free tier that easily covers bot traffic. Note the project ID (e.g.,my-chat-bot-123). You’ll use it in every subsequent
step.
Step 2: Enable two APIs
In the console, go to APIs & Services → Library and enable:- Google Chat API
- Cloud Pub/Sub API
Step 3: Create a Service Account
IAM & Admin → Service Accounts → Create Service Account.- Name:
mibyan-chat-bot - Skip the “Grant this service account access to project” step. IAM on the specific subscription is all you need — do NOT grant project-level Pub/Sub roles.
~/.mibyan/google-chat-sa.json, chmod 600).
Step 4: Create the Pub/Sub topic and subscription
Pub/Sub → Topics → Create topic.- Topic ID:
mibyan-chat-events - Leave the defaults for everything else.
- Subscription ID:
mibyan-chat-events-sub - Delivery type: Pull
- Message retention: 7 days (so backlog survives a mibyan restart)
- Leave the rest default.
Step 5: IAM binding on the topic (critical)
On the topic (not the subscription), add an IAM principal:- Principal:
chat-api-push@system.gserviceaccount.com - Role:
Pub/Sub Publisher
Step 6: IAM binding on the subscription
On the subscription, add your own Service Account as a principal:- Principal:
mibyan-chat-bot@<your-project>.iam.gserviceaccount.com - Role:
Pub/Sub Subscriber
Pub/Sub Viewer on the same subscription — Mibyan calls
subscription.get() at startup as a reachability check.
Step 7: Configure the Chat app
Go to APIs & Services → Google Chat API → Configuration.- App name: whatever you want users to see (“Mibyan” is reasonable).
- Avatar URL: any public PNG (Google has some defaults).
- Description: a short sentence shown in the app directory.
- Functionality: enable Receive 1:1 messages and Join spaces and group conversations.
- Connection settings: select Cloud Pub/Sub, enter the topic name
projects/<your-project>/topics/mibyan-chat-events. - Visibility: restrict to your workspace (or specific users) — do not publish to everyone while you’re testing.
Step 8: Install the bot in a test space
Open Google Chat in a browser. Start a DM with your app by searching for its name in the + New Chat menu. The first time you message it, Google sends anADDED_TO_SPACE event that Mibyan uses to cache the bot’s own users/{id} for
self-message filtering.
Step 9: Configure Mibyan
Add the Google Chat section to~/.mibyan/.env:
GOOGLE_CLOUD_PROJECT, and the SA path falls
back to GOOGLE_APPLICATION_CREDENTIALS — use whichever convention you prefer.
Under a multi-profile gateway, every
GOOGLE_CHAT_* setting is read from the routed profile’s own .env; a
secondary profile never inherits the default profile’s project, subscription,
or service account. If a profile has no SA configured while the process
environment carries one for another profile, the adapter refuses to fall back
to Application Default Credentials (which would authenticate as that other
profile) and logs an explicit error instead — put
GOOGLE_CHAT_SERVICE_ACCOUNT_JSON in that profile’s .env.
Install the Google Chat adapter dependencies through its maintained installer.
It applies the same pinned security floors used by the runtime checks:
--install-deps asks PM to add the google-chat extra to the managed Python
environment (pm.sync_venv); restart the gateway after it finishes so the new
environment is active. On Docker / hosted images the venv is read-only and
on-demand installs are disabled (mibyan_DISABLE_LAZY_INSTALLS=1), so this
step cannot add anything there. The published image bakes the [google-chat]
extra instead, so a fresh container does not need a first-boot install.
Start the gateway:
Customizing the working-state marker
The marker text is configurable viatyping_status_text in
~/.mibyan/config.yaml — e.g. a kitten assistant named Ada:
typing_indicator: false to
disable the marker entirely.
Formatting and capabilities
Google Chat renders a limited markdown subset:
The agent’s system prompt includes a Google Chat–specific hint so it knows these
limits and avoids formatting that won’t render.
Message size limit: 4000 characters per message. Longer agent responses are
automatically split across multiple messages.
Thread support: when a user replies inside a thread, Mibyan detects the
thread.name and posts its reply in the same thread, so each thread gets a
separate Mibyan session.
Clarify questions as interactive cards
When the agent asks a multiple-choice clarify question, the adapter renders it as a native Card v2 with one button per choice plus an “Other / type answer” button, instead of a plain numbered text list. Clicking a button answers the question directly (CARD_CLICKED events route
the choice back into the waiting session). If the card fails to send, or the
question has no fixed choices, the adapter falls back to the standard text
clarify. No configuration needed.
Step 10: Native attachment delivery (optional)
Out of the box the bot can post text, inline images via URL, and download cards for audio/video/documents. To deliver native Chat attachments — the same file widget you get when a human drags-and-drops a file — each user authorizes the bot once via a per-user OAuth flow.Why a separate flow
Google Chat’smedia.upload endpoint hard-rejects service-account auth:
This method doesn’t support app authentication with a service account. Authenticate with a user account.There’s no IAM role or scope that fixes this. The endpoint only accepts user credentials. So the bot has to act as a user whenever it uploads a file — specifically, as the user who asked for the file.
One-time setup (per profile)
- Go to APIs & Services → Credentials in the same GCP project.
- Create credentials → OAuth client ID → Desktop app.
- Download the JSON. Move it onto the host that runs Mibyan.
- Register the client with Mibyan (run under the profile you want it scoped to):
~/.mibyan/google_chat_user_client_secret.json for the default profile). The
client secret is profile-scoped, not shared across profiles — each profile
registers its own. This is deliberate: profiles are isolated auth boundaries, so
two profiles can point at different Google OAuth apps / accounts. Register it
once per profile that needs Google Chat attachment delivery.
Per-user authorization (in chat)
Each user runs the flow once, in their own DM with the bot:- They send
/setup-filesto the bot. It replies with status and the next step. - They send
/setup-files start. The bot replies with an OAuth URL. - They open the URL, click Allow, and watch the browser fail to load
http://localhost:1/?...&code=.... That failure is expected — the auth code is in the URL bar. - They copy the failed URL (or just the
code=...value) and paste it back into chat as/setup-files <PASTED_URL>. The bot exchanges it for a refresh token.
~/.mibyan/google_chat_user_tokens/<sanitized_email>.json.
Subsequent file requests in that user’s DM use their token, so the bot
uploads as them and the message lands in their space.
To revoke later: /setup-files revoke deletes only that user’s token. Other
users’ tokens are untouched.
Scope
The flow requests exactly one scope:chat.messages.create. That covers both
media.upload and the messages.create that references the uploaded
attachmentDataRef. No Drive, no broader Chat scopes — this is least-privilege
on purpose.
Multi-user behavior
When the asker has no per-user token yet, the bot falls back to a legacy single-user token at~/.mibyan/google_chat_user_token.json (if present from
a pre-multi-user install). When neither is available, the bot posts a clear
text notice telling the asker to run /setup-files.
A user revoking only clears their own slot. A 401/403 from one user’s token
evicts only that user’s cache. Users don’t disrupt each other.
Troubleshooting
Bot stays silent after sending “hola.”- Check the Pub/Sub subscription has undelivered messages in the console.
If it does, Mibyan isn’t authenticated — verify
GOOGLE_CHAT_SERVICE_ACCOUNT_JSONand that the SA is listed asPub/Sub Subscriberon the subscription. - If the subscription has zero messages, Google Chat isn’t publishing.
Double-check the IAM binding on the topic:
chat-api-push@system.gserviceaccount.commust havePub/Sub Publisher. - Check
mibyan gatewaylogs for[GoogleChat] Connected. If you see[GoogleChat] Config validation failed, the error message tells you which env var to fix.
[GoogleChat] Pub/Sub stream died — if these repeat, your SA
credentials may have been rotated or the subscription deleted. After 10 attempts
the adapter marks itself fatal.
“403 Forbidden” on every outbound message.
The bot was removed from the space, or you revoked it in the Chat API console.
Re-install it in the space (the next ADDED_TO_SPACE event will re-enable
messaging automatically).
Too many “Rate limit hit” warnings.
The Chat API’s default quotas allow 60 messages per space per minute. If your
agent produces long streaming responses that exceed that, the adapter retries
with exponential backoff — but you’ll still see user-visible latency. Consider
concise responses or raising the quota in the GCP console.
Bot keeps posting the “/setup-files” notice instead of files.
The asker has no per-user OAuth token and there’s no legacy fallback. Run
/setup-files in their DM and follow Step 10. After the exchange completes
the next file request uploads natively without a gateway restart.
/setup-files start says “No client credentials stored.”
The one-time setup wasn’t done for this profile (the client secret is
profile-scoped, so a registration under one profile won’t be seen by another).
From a terminal, run it under the profile the gateway uses:
/setup-files start again.
/setup-files <PASTED_URL> says “Token exchange failed.”
The auth code is single-use and short-lived (typically a few minutes). Send
/setup-files start to get a fresh URL and retry.
Security notes
- Service Account scope: the adapter requests
chat.botandpubsubscopes. IAM should be the actual enforcement — grant your SA the minimum (roles/pubsub.subscriber+roles/pubsub.vieweron the subscription), not project-level or org-level Pub/Sub roles. - Attachment download protection: Mibyan will only attach the SA bearer
token to URLs whose host matches a short allowlist of Google-owned domains
(
googleapis.com,drive.google.com,lh[3-6].googleusercontent.com, and a few others). Any other host is rejected before the HTTP request, to protect against SSRF scenarios where a crafted event could redirect the bearer token to the GCE metadata service. - Redaction: Service Account emails, subscription paths, and topic paths
are stripped from log output by
agent/redact.py. The debug envelope dump (GOOGLE_CHAT_DEBUG_RAW=1) routes through the same redaction filter and logs at DEBUG level. - Compliance: if you plan to connect this bot to a regulated workspace (anything with a data-residency or AI-governance policy), get that approval before the first install.
- User OAuth scope: the per-user attachment flow requests only
chat.messages.create— the minimum that coversmedia.uploadplus the follow-upmessages.create. Tokens are persisted as plain JSON at~/.mibyan/google_chat_user_tokens/<sanitized_email>.json(filesystem permissions are the protection — same model as the SA key file). Each token is owned by exactly one user; revoke is scoped to that user.

