Google Chat runs as the official @openclaw/googlechat plugin: DMs and spaces through Google Chat API webhooks (HTTP endpoint only, no Pub/Sub).

Install

apus plugins install @openclaw/googlechat

Local checkout (when running from a git repo):

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

Quick setup (beginner)

  1. Create a Google Cloud project and enable the Google Chat API.
  2. Create a Service Account:
    • Press Create Credentials > Service Account.
    • Name it whatever you want (e.g., apus-chat).
    • Leave permissions and principals blank (Continue, then Done).
  3. Create and download the JSON key:
    • Click the new service account > Keys tab > Add Key > Create new key > JSON > Create.
  4. Store the downloaded JSON file on your gateway host (e.g., ~/.apus/googlechat-service-account.json).
  5. Create a Google Chat app in the Google Cloud Console Chat Configuration:
    • Fill in Application info (app name, avatar URL, description).
    • Enable Interactive features.
    • Under Functionality, check Join spaces and group conversations.
    • Under Connection settings, select HTTP endpoint URL.
    • Under Triggers, select Use a common HTTP endpoint URL for all triggers and set it to your public gateway URL followed by /googlechat (see Public URL).
    • Under Visibility, check Make this Chat app available to specific people and groups in <Your Domain> and enter your email address.
    • Click Save.
  6. Enable the app status: refresh the page, find App status, set it to Live - available to users, and Save again.
  7. Configure Apus with the service account and the webhook audience (must match the Chat app config):
    • Env: GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json (default account only), or
    • Config: see Config highlights. apus channels add --channel googlechat also accepts --audience-type, --audience, --webhook-path, and --webhook-url.
  8. Start the gateway. Google Chat will POST to your webhook path (default /googlechat).

Add to Google Chat

Once the gateway is running and your email is on the visibility list:

  1. Go to Google Chat.
  2. Click the + (plus) icon next to Direct Messages.
  3. Search for the App name you configured in the Google Cloud Console.
    • The bot does not appear in the Marketplace browse list because it is a private app. Search for it by name.
  4. Select the bot, click Add or Chat, and send a message.

Public URL (Webhook-only)

Google Chat webhooks require a public HTTPS endpoint. For security, expose only the /googlechat path to the internet and keep the Apus dashboard and other endpoints private.

Option A: Tailscale Funnel (Recommended)

Use Tailscale Serve for the private dashboard and Funnel for the public webhook path.

  1. Check what address your gateway is bound to:

    # Linux (iproute2):
    ss -tlnp | grep 18789
    
    # macOS (no ss):
    lsof -iTCP:18789 -sTCP:LISTEN
    

    Note the IP (e.g., 127.0.0.1, 0.0.0.0, or a Tailscale 100.x.x.x address).

  2. Expose the dashboard to the tailnet only (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. Expose only the webhook path 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. If prompted, visit the authorization URL shown in the output to enable Funnel for this node.

  5. Verify:

    tailscale serve status
    tailscale funnel status
    

Your public webhook URL is https://<node-name>.<tailnet>.ts.net/googlechat. The dashboard stays tailnet-only at https://<node-name>.<tailnet>.ts.net:8443/. Use the public URL (without :8443) in the Google Chat app config.

Note: This configuration persists across reboots. Remove it later with tailscale funnel reset and tailscale serve reset.

Option B: Reverse Proxy (Caddy)

Proxy only the webhook path:

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

Requests to your-domain.com/ are ignored or 404, while your-domain.com/googlechat routes to Apus.

Option C: Cloudflare Tunnel

Configure the tunnel ingress rules to route only the webhook path:

How it works

  1. Google Chat POSTs JSON to the gateway webhook path (POST only, JSON content type required, per-IP rate limited).
  2. Apus authenticates every request before dispatch:
    • Chat app events carry Authorization: Bearer <token>. The token is verified before the full body is parsed.
    • Google Workspace Add-on events carry the token in the body (authorizationEventObject.systemIdToken). Apus reads them under a stricter pre-auth budget (16 KB, 3 s) before verification.
  3. The token is checked against audienceType + audience:
    • audienceType: "app-url" → audience is your HTTPS webhook URL.
    • audienceType: "project-number" → audience is the Cloud project number.
    • Add-on tokens under app-url additionally require appPrincipal set to the app's numeric OAuth 2.0 client ID (21 digits, not an email). Otherwise verification fails with a logged warning.
  4. Messages route by space:
    • Spaces get per-space sessions agent:<agentId>:googlechat:group:<spaceId>. With replyToMode: "first" or "all", automatic replies stay in the inbound thread. The default and explicit "off" modes keep replies top-level, whether typing indicators are enabled or disabled.
    • DMs collapse into the agent's main session by default. Set session.dmScope for per-peer DM sessions (see Session).
  5. DM access is pairing by default. Unknown senders receive a pairing code. Approve with:
    • apus pairing approve googlechat <code>
  6. Group spaces require @-mention by default. Mentions are detected from Chat USER_MENTION annotations targeting the app. Set botUser (e.g., users/1234567890) if detection needs the app's user resource name.
  7. When an exec or plugin approval starts from Google Chat and a stable users/<id> approver is configured, Apus posts a native approval card (cardsV2) in the originating space or thread. Card buttons carry opaque callback tokens. The manual /approve <id> <decision> prompt appears only when native delivery is unavailable.

Inbound durability

After request authentication, Apus removes the add-on authorization object from storage and durably queues Google Chat MESSAGE events before returning 200. A persistence failure returns 503, allowing Google Chat to retry instead of acknowledging an event that could be lost. A durably queued 200 carries x-apus-delivery-accepted: durable. Non-message action acks and error responses omit the marker, so reverse proxies can require it to distinguish durable acceptance from a generic 200.

Pending or retryable messages survive a Gateway restart, remain serialized per space, and use the Google Chat message resource name to suppress duplicate queue entries while the active or retained completion record exists. Non-message actions keep their existing detached webhook path and do not receive this durable-queue guarantee. Delivery remains at least once across the queue-to-agent boundary, so a crash during handoff can replay a turn.

Targets

Use these identifiers for delivery and allowlists:

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:

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

Secrets reference details: Secrets Management.

Troubleshooting

405 Method Not Allowed

If Google Cloud Logs Explorer shows errors like:

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

The webhook handler is not registered. Common causes:

  1. Channel not configured: the channels.googlechat section is missing. Verify with:

    apus config get channels.googlechat
    

    If it returns "Config path not found", add the configuration (see Config highlights).

  2. Plugin not enabled: check plugin status:

    apus plugins list | grep googlechat
    

    If it shows "disabled", run apus plugins enable googlechat and check the application result.

  3. Configuration not applied: check hot reload status and Gateway logs. Start the Gateway if it is offline. If you changed the service environment, restart the Gateway to load it.

Verify the channel is running:

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

Other issues

Related