Google Chat Roadmap
Developer notes for the Google Chat upstream connector: how Triage’s diagnostic runs as a Chat app, rendered with Cards v2 over the Chat API. The gateway’s Google Chat renderer already ships in the reference implementation; the hosted transport is on the roadmap. Every platform fact here is taken from Google’s own machine-readable discovery document and developer documentation.
The public platform facts the Intake Gateway’s Google Chat renderer relies on. Method names, scopes and field names are taken from the Chat API v1 discovery document (revision 2026-08-15) and the pages listed under Developer references.
| App & install | A Chat app is configured in a Google Cloud project (Chat API configuration) and published to a Workspace organisation or the Marketplace. Base URL https://chat.googleapis.com, REST v1. |
|---|---|
| Authentication | App authentication with a service account for the app's own actions (scope chat.bot, or the granular chat.messages.create / chat.messages); user authentication with OAuth when acting for a person. Scopes are per method: the discovery document lists them on every call. |
| Post a question | spaces.messages.create: POST /v1/{parent=spaces/*}/messages with a cardsV2 payload. Optional requestId makes the create idempotent: sending an existing request ID returns the message that ID already created instead of posting a second one. |
| Lock in place | spaces.messages.patch (PATCH /v1/{name=spaces/*/messages/*}) with updateMask=cardsV2 replaces the card in place. A custom messageId at create time means the app can update or delete later without storing Chat's own resource name. |
| Question rendering | Cards v2: a message carries a list of {cardId, card} pairs, each card at most 32 KB. Widgets used for a diagnostic are textParagraph, decoratedText, buttonList, divider, plus textInput, selectionInput and dateTimePicker for form input. |
| Interactivity | A button's onClick.action names a function and parameters. Chat posts a CARD_CLICKED interaction event back to the app, which reads commonEventObject.invokedFunction, .parameters and, for form widgets, .formInputs.WIDGET_NAME. |
| Interaction events | The app receives typed events: MESSAGE (including direct messages, @mentions and slash commands), ADDED_TO_SPACE, REMOVED_FROM_SPACE, CARD_CLICKED, WIDGET_UPDATED, APP_COMMAND, APP_HOME and SUBMIT_FORM. |
| Transport in | An HTTPS endpoint, a Pub/Sub topic, or Apps Script. A synchronous reply must be posted within 30 seconds; anything slower answers asynchronously by calling the Chat API instead. |
| Request verification | Every delivery carries a bearer JWT issued and signed by chat@system.gserviceaccount.com, with the audience set to the app's project number. The app fetches the signing keys from googleapis.com/service_accounts/v1/jwk/chat@system.gserviceaccount.com, verifies issuer and audience, and answers 401 when verification fails. |
| Dialogs | For multi-field input the app answers with actionResponse.type = DIALOG and a dialogAction.dialog.body card. Dialog interactions arrive as REQUEST_DIALOG, SUBMIT_DIALOG or CANCEL_DIALOG, with isDialogEvent set on the event. |
| Response types | actionResponse.type selects the behaviour: NEW_MESSAGE, UPDATE_MESSAGE, UPDATE_USER_MESSAGE_CARDS, UPDATE_WIDGET, DIALOG and REQUEST_CONFIG (which sends the user an authorisation or configuration URL). |
| Privacy inside a shared space | privateMessageViewer on a message makes it visible to one named user only, so a diagnostic can run inside a busy space without the questions reaching everyone in it. |
| Finding the conversation | spaces.findDirectMessage locates the direct-message space with a person; spaces.setup and spaces.create open a new space, and spaces.members manages who is in it. |
| Reading events back | spaces.spaceEvents.list and .get return typed space events such as google.workspace.chat.message.v1.created and .batchCreated, so a missed push can be recovered by reading history rather than by hoping the delivery arrives. |
| Rate limits | Quotas are per space and per project. Google's usage limits page states 60 writes per minute per space, shared across every Chat app in that space; an exceeded quota returns 429 and should be retried with exponential backoff. |
| Identity | The acting person arrives as user on the event (name, displayName, email where the scope allows), which is what the gateway records as the requester. |
One question per card, a fixed order, and an answer that locks once given. The same gateway core drives Teams, Slack and Google Chat: only the renderer differs.
| Step | What happens on the platform |
|---|---|
| 1 | The requester opens the Triage app in Google Chat, or @mentions it in a space. Chat delivers a MESSAGE or APP_COMMAND interaction event to the gateway endpoint, carrying a bearer JWT. |
| 2 | The gateway verifies the JWT against Google's published keys, checks issuer and audience, and rejects anything unverifiable with 401. Only then does it read the event. |
| 3 | It resolves the requester from user on the event, and the conversation from space and thread. |
| 4 | It posts the first question with spaces.messages.create: one Cards v2 card, one question, buttons carrying the answer values. In a shared space the message sets privateMessageViewer so only the requester sees it. |
| 5 | A click delivers a CARD_CLICKED event within the 30 second window. The gateway reads commonEventObject.invokedFunction and .parameters, records the answer, and calls spaces.messages.patch with updateMask=cardsV2 to replace the card with a locked version that has no buttons. |
| 6 | Where a step needs several fields at once, the app answers actionResponse.type = DIALOG instead: Chat opens a modal, the requester completes it, and SUBMIT_DIALOG returns every value in commonEventObject.formInputs. |
| 7 | On the final answer the gateway calls the Triage decision core. The Intent Record is minted, and the completion card carries the channel, the confidence and the intent:// chip. |
| 8 | When composerID publishes downstream, the gateway updates the completion card with a button that deep-links to the created record. Nothing about the diagnostic leaves Google Chat: the destination system receives the Intent ID, not the answers. |
Google’s official developer documentation. The REST facts on this page come from the machine-readable discovery document, which is the same artefact Google’s own client libraries are generated from.
The gateway’s Google Chat renderer and its contract tests live in upstream/google_chat_cards.py in this repository: the same gateway core, a different renderer. Locking is structural on this surface too: the answered card is replaced via updateMask=cardsV2 with a version that has no buttons.