Google Chat Webhook Handler

Google Workspace integration for BetterFiles — receives events from Google Chat spaces, verifies JWT identity (only Google’s service accounts are accepted), and routes to command handlers, slash command handlers, and link preview logic. Mounted on the quo-handler-enhanced Express app via mountGchatRoutes(app).

Endpoint

PathProviderAuth
https://discord-bot.reri.co/webhook/gchatGoogle ChatJWT Bearer

Port: Shares 18792 with quo-handler-enhanced.js (mounted via mountGchatRoutes(app)) Not a standalone service — routes added to the OpenPhone/Quo handler Express app.

Configure in Google Chat API:

  • App Configuration → Connection settings → App URL: https://discord-bot.reri.co/webhook/gchat

Auth method

Google JWT Bearer token — NO HMAC. Google sends a signed JWT in the Authorization: Bearer <token> header from its Chat service account.

Verification via OAuth2Client.verifyIdToken() from google-auth-library:

Audience: https://discord-bot.reri.co/webhook/gchat
Allowed issuers:
  - chat@system.gserviceaccount.com
  - service-874926066580@gcp-sa-gsuiteaddons.iam.gserviceaccount.com

Any request with a missing, expired, wrong-audience, or unauthorized-issuer token is rejected with 401. This is stronger than HMAC for external APIs — the token is cryptographically signed by Google’s private key.

Payload shape

Google Chat sends JSON event objects:

Event typeDescription
ADDED_TO_SPACEBot added to a new Chat space
REMOVED_FROM_SPACEBot removed from a space
MESSAGEUser sends text or structured card message
CARD_CLICKEDUser interacts with an interactive card

Key fields:

{
  "type": "MESSAGE",
  "space": { "name": "spaces/AAAA...", "type": "ROOM" },
  "message": {
    "text": "string",
    "slashCommand": { "commandId": "number" }
  },
  "user": { "email": "string", "displayName": "string" }
}

Downstream dispatch

gchat-handler.js (mounted on quo-handler-enhanced)
 ├─ verifyGoogleChatToken(req) → OAuth2Client.verifyIdToken() → 401 if invalid
 ├─ parseCommand(message) → gchat-commands.js
 ├─ handleCommand() → workspace-betterfiles/scripts/lib/gchat-commands.js
 ├─ handleSlashCommand() → slash command routing
 ├─ handleLinkPreview() → URL metadata expansion for link cards
 └─ getChatClient() → workspace-betterfiles/scripts/lib/gchat-client.js
     → async message updates / card replacements

State store: workspace-betterfiles/data/gchat-state.json — persists space membership and session state.

Dedup strategy

State managed in gchat-state.json. ADDED_TO_SPACE / REMOVED_FROM_SPACE events are idempotent (upsert space record). MESSAGE events are not deduped — Google Chat guarantees at-most-once delivery for push events.

Audit trail

  • Authentication failures: Any JWT verification failure (missing token, wrong audience, unauthorized issuer) is logged as a security exception before returning 401.
  • Logging: Via parent quo-handler-enhanced application logging middleware.
  • State persistence: gchat-state.json serves as an implicit audit trail of space membership changes.
  • webhook-architecture — Cross-handler governance; gchat uses Google JWT Bearer (not HMAC) — do not attempt to add HMAC verification
  • _summary — BetterFiles agent is the primary consumer; gchat-commands routes to BetterFiles workspace scripts
  • cron-timer-registry — Handler shares port 18792 with openphone-webhook.service