Google Chat Integration: Setup and Configuration Guide

Learn how to install and configure the Google Chat plugin for OpenClaw, including service account setup and webhook handling for direct messages and spaces.

Read this when

  • Working on Google Chat channel features

Google Chat is delivered through the official @openclaw/googlechat plugin, handling both direct messages and spaces via Google Chat API webhooks. These webhooks use HTTP endpoints exclusively, with no Pub/Sub support.

Install

openclaw plugins install @openclaw/googlechat

For a local checkout, when running directly from a git repository:

openclaw plugins install ./path/to/local/googlechat-plugin

Quick setup (beginner)

  1. Set up a Google Cloud project and turn on the Google Chat API.
  2. Set up a Service Account:
    • Choose Create Credentials followed by Service Account.
    • Assign any name you prefer (for instance, openclaw-chat).
    • Skip permissions and principals by selecting Continue and then Done.
  3. Generate and download the JSON key:
    • Open the service account you just made, go to the Keys tab, select Add Key, pick Create new key, choose JSON, and hit Create.
  4. Place the downloaded JSON file somewhere on your gateway host (like ~/.openclaw/googlechat-service-account.json).
  5. Build a Google Chat app using the Google Cloud Console Chat Configuration:
    • Complete the Application info fields, covering the app name, avatar URL, and description.
    • Switch on Interactive features.
    • In Functionality, tick Join spaces and group conversations.
    • For Connection settings, pick HTTP endpoint URL.
    • Under Triggers, choose Use a common HTTP endpoint URL for all triggers, then enter your public gateway URL with /googlechat appended (check Public URL for details).
    • In Visibility, tick Make this Chat app available to specific people and groups in <Your Domain> and type in your email address.
    • Press Save.
  6. Activate the app: reload the page, locate App status, switch it to Live - available to users, and hit Save once more.
  7. Point OpenClaw at the service account and webhook audience, ensuring they align with the Chat app setup:
    • Through env: GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json (only works with the default account), or
    • Through config: refer to Config highlights. Additionally, openclaw channels add --channel googlechat supports --audience-type, --audience, --webhook-path, and --webhook-url.
  8. Launch the gateway. Google Chat will then send POST requests to your webhook path, which defaults to /googlechat.

Add to Google Chat

After the gateway is up and your email appears on the visibility list:

  1. Open Google Chat.
  2. Select the + (plus) icon beside Direct Messages.
  3. Look up the App name you defined in the Google Cloud Console.
    • Since this is a private app, it won't show up in the Marketplace browse list; you'll need to search by name.
  4. Choose the bot, press Add or Chat, and then send a message.

Public URL (Webhook-only)

A public HTTPS endpoint is mandatory for Google Chat webhooks. For safety, make only the /googlechat path reachable from the internet, and keep the OpenClaw dashboard plus all other endpoints off the public network.

Option A: Tailscale Funnel (Recommended)

Tailscale Serve handles the private dashboard, while Funnel covers the public webhook path.

  1. Determine the address your gateway is bound to:

    ss -tlnp | grep 18789
    

    Write down the IP, such as 127.0.0.1, 0.0.0.0, or a Tailscale 100.x.x.x address.

  2. Restrict the dashboard to the tailnet only, using port 8443:

    # If bound to localhost (127.0.0.1 or 0.0.0.0):
    tailscale serve --bg --https 8443 http://127.0.0.1:18789
    
    # If bound to a Tailscale IP only:
    tailscale serve --bg --https 8443 http://100.x.x.x:18789
    
  3. Make just the webhook path available publicly:

    # If bound to localhost (127.0.0.1 or 0.0.0.0):
    tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat
    
    # If bound to a Tailscale IP only:
    tailscale funnel --bg --set-path /googlechat http://100.x.x.x:18789/googlechat
    
  4. When asked, open the authorization URL from the output to enable Funnel on this node.

  5. Confirm everything works:

    tailscale serve status
    tailscale funnel status
    

Your public webhook URL sits at https://<node-name>.<tailnet>.ts.net/googlechat, while the dashboard remains tailnet-only at https://<node-name>.<tailnet>.ts.net:8443/. In the Google Chat app config, use the public URL without :8443.

Note: This setup survives reboots. To undo it later, run tailscale funnel reset and tailscale serve reset.

Option B: Reverse Proxy (Caddy)

Forward only the webhook path:

your-domain.com {
    reverse_proxy /googlechat* localhost:18789
}

Traffic hitting your-domain.com/ gets ignored or returns 404, whereas your-domain.com/googlechat is routed to OpenClaw.

Option C: Cloudflare Tunnel

Set up the tunnel ingress rules so only the webhook path is forwarded:

  • Path: /googlechat -> http://localhost:18789/googlechat
  • Default rule: HTTP 404 (Not Found)

How it works

  1. Google Chat delivers JSON payloads to the gateway webhook endpoint, restricted to POST requests with a JSON content type and subject to per-IP rate limiting.
  2. Prior to dispatch, OpenClaw validates every incoming request:
    • Chat app events include Authorization: Bearer <token>; this token gets verified before the full payload is processed.
    • Google Workspace Add-on events embed the token within the body (authorizationEventObject.systemIdToken) and are parsed under a tighter pre-auth limit (16 KB, 3 s) prior to validation.
  3. The token is matched against audienceType plus audience:
    • audienceType: "app-url" → the audience corresponds to your HTTPS webhook URL.
    • audienceType: "project-number" → the audience corresponds to the Cloud project number.
    • Add-on tokens under app-url also demand that appPrincipal be set to the app's numeric OAuth 2.0 client ID (21 digits, not an email); otherwise, verification fails and a warning is logged.
  4. Routing of messages depends on the space:
    • Spaces receive dedicated sessions agent:<agentId>:googlechat:group:<spaceId>; replies are directed to the message thread.
    • DMs merge into the agent's primary session by default; configure session.dmScope to enable per-peer DM sessions (refer to Session).
  5. DM access defaults to pairing. Unrecognized senders get a pairing code; authorize using:
    • openclaw pairing approve googlechat <code>
  6. Group spaces require an @-mention by default. Mentions are identified from Chat USER_MENTION annotations that target the app; set botUser (for instance, users/1234567890) if detection relies on the app's user resource name.
  7. When an exec or plugin approval originates from Google Chat and a stable users/<id> approver is set, OpenClaw sends a native approval card (cardsV2) to the originating space or thread. Card buttons contain opaque callback tokens; the manual /approve <id> <decision> prompt appears only when native delivery is not possible.

Inbound durability

Once request authentication finishes, OpenClaw deletes the add-on authorization object from storage and durably queues Google Chat MESSAGE events before returning 200. A persistence failure triggers 503, which lets Google Chat retry rather than acknowledge an event that might be lost. A durably queued 200 includes x-openclaw-delivery-accepted: durable; non-message action acknowledgments and error responses omit this marker, so reverse proxies can require it to tell durable acceptance apart from a generic 200.

Pending or retryable messages endure a Gateway restart, stay serialized per space, and leverage the Google Chat message resource name to prevent duplicate queue entries while the active or retained completion record exists. Non-message actions retain their existing detached webhook path and are excluded from this durable-queue guarantee. Delivery is at least once across the queue-to-agent boundary, so a crash during handoff may replay a turn.

Targets

For delivery and allowlists, use these identifiers:

  • Direct messages: users/<userId> (recommended).
  • Spaces: spaces/<spaceId>.
  • Raw email name@example.com is changeable and only applies to allowlist matching when channels.googlechat.dangerouslyAllowNameMatching: true.
  • Deprecated: users/<email> is interpreted as a user id, not an email allowlist entry.
  • Prefixes googlechat:, google-chat:, and gchat: are accepted and removed.

Config highlights

{
  channels: {
    googlechat: {
      enabled: true,
      serviceAccountFile: "/path/to/service-account.json",
      // or serviceAccount: { source: "file", provider: "filemain", id: "/channels/googlechat/serviceAccount" }
      audienceType: "app-url",
      audience: "https://gateway.example.com/googlechat",
      appPrincipal: "123456789012345678901", // add-on verification only; numeric OAuth client ID
      webhookPath: "/googlechat",
      botUser: "users/1234567890", // optional; helps mention detection
      allowBots: false,
      dmPolicy: "pairing",
      allowFrom: ["users/1234567890"],
      groupPolicy: "allowlist",
      groups: {
        "spaces/AAAA": {
          enabled: true,
          requireMention: true,
          users: ["users/1234567890"],
          systemPrompt: "Short answers only.",
        },
      },
      typingIndicator: "message",
      mediaMaxMb: 20,
    },
  },
}

Notes:

  • Service account credentials can be supplied via serviceAccountFile (a path) or serviceAccount (an inline JSON string, object, or env/file/exec/store SecretRef). Environment variables GOOGLE_CHAT_SERVICE_ACCOUNT (inline JSON) and GOOGLE_CHAT_SERVICE_ACCOUNT_FILE (path) only affect the default account. For multiple accounts, channels.googlechat.accounts.<id> uses the same keys, including per-account serviceAccount SecretRefs.
  • When an account omits dmPolicy and groupPolicy, those values fall back to the channel root; explicit account-level policies take precedence. The root defaults to pairing and allowlist respectively. Shared settings from accounts.default rank below the root; its credentials, enabled, and dangerouslyAllowNameMatching are not passed down to named accounts.
  • With webhookPath left empty, the default webhook path is /googlechat; alternatively, webhookUrl can provide the path.
  • Group keys need stable space ids (spaces/<spaceId>). Keys based on display names are deprecated and trigger a log warning.
  • dangerouslyAllowNameMatching turns on mutable email principal matching for allowlists as a break-glass compatibility mode; doctor flags any email entries with a warning.
  • Google Chat reaction actions stay unavailable. The plugin authenticates as a service account, but reaction endpoints demand user authentication. Use openclaw doctor --fix to drop unsupported legacy reaction settings.
  • Native approval cards rely on Google Chat cardsV2 button clicks, not reaction events. Approvers are drawn from allowFrom or defaultTo and must be stable numeric users/<id> values.
  • Message actions only expose text via send. Since Google Chat attachment upload requires user authentication and this plugin uses service-account authentication, outbound file upload is not exposed.
  • typingIndicator: message (the default) posts a _<Bot> is typing..._ placeholder and then edits it into the first reply; none disables that behavior; reaction needs user OAuth and currently reverts to message with a logged error under service-account auth.
  • Inbound attachments (the first per message) are fetched through the Chat API into the media pipeline, limited by mediaMaxMb (default 20). Google Drive files are skipped; the agent gets an unavailable-attachment notice asking for a direct file upload. Other unsupported attachment sources receive the same upload guidance. Messages with multiple attachments include a counted notice for the extra attachments that were not processed. Oversize attachments keep their size-limit notice.
  • Bot-authored messages are ignored unless allowBots: true is set, in which case accepted bot messages use shared bot loop protection: set channels.defaults.botLoopProtection, then override with channels.googlechat.botLoopProtection or channels.googlechat.groups.<space>.botLoopProtection.

Custom emoji listing is not possible because Google Chat's customEmojis.list endpoint requires user authentication with the chat.customemojis or chat.customemojis.readonly scope. This plugin authenticates solely as a service account with the chat.bot scope, which cannot reach that endpoint.

For secrets reference details, see Secrets Management.

Troubleshooting

405 Method Not Allowed

If Google Cloud Logs Explorer reports errors like:

status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed

The webhook handler is not registered. Typical reasons:

  1. Channel not configured: the channels.googlechat section is absent. Confirm with:

    openclaw config get channels.googlechat
    

    If the response is "Config path not found", add the configuration (see Config highlights).

  2. Plugin not enabled: inspect plugin status:

    openclaw plugins list | grep googlechat
    

    If it says "disabled", add plugins.entries.googlechat.enabled: true to your config.

  3. Gateway not restarted after config changes:

    openclaw gateway restart
    

Check that the channel is running:

openclaw channels status
# Should show: Google Chat default: enabled, configured, ...

Other issues

  • openclaw channels status --probe reveals auth errors and missing audience config (audience and audienceType are both mandatory).
  • If no messages arrive, verify the Chat app's webhook URL and trigger configuration.
  • If mention gating blocks replies, set botUser to the app's user resource name and review requireMention.
  • openclaw logs --follow while sending a test message shows whether requests reach the gateway.
1,971 words · updated Sep 1, 2026