Google Chat Sensor
What it ingests
Spaces, their members, and their messages — top-level posts and thread replies — from every Workspace user in the org. The Chat API shows each user only the spaces they belong to, so coverage fans out across every member by impersonating each one, the way Gmail and Google Meet do. A space several members share is read once.
| From Google Chat | Ontology |
|---|---|
| A space | Channel (googlechat:space:spaces/…), the same type Slack and Teams channels use |
| A message or thread reply | Message (googlechat:message:spaces/…/messages/…); Message BELONGS_TO Channel. Replies carry thread_name and is_reply |
| The sender | Person AUTHORED Message |
| An @-mention | Message MENTIONS Person |
| A joined member | Person MEMBER_OF Channel |
| Message text (LLM pass) | Topic / Project / Organization / Person, through the Google Chat messages extraction prompt |
People link to the Person entities other sources already created (Slack, GitHub, Meet, Gmail) by email, and are keyed on their Google account id so a name change doesn't split them.
Named spaces only, by default. Group chats and direct messages are private conversations, so they're ingested only if you add them to space_types.
Messages from Chat apps (bots) are skipped unless include_app_messages is true. Deleted messages are skipped. Message text passes through Corveil's content redaction (keys, tokens, signed-URL parameters) before it's stored.
Credential
References a google_service_account_json credential by id — the same type Meet, Drive, Calendar, and Gmail already use. No new service account. The secret is the full service-account key JSON.
The credential is required. Unlike Meet or Gmail there's no ambient (ADC) mode: Chat without a delegated user only sees spaces a Chat app was added to.
The domain-wide delegation grant must include all three Chat scopes:
https://www.googleapis.com/auth/chat.spaces.readonly
https://www.googleapis.com/auth/chat.messages.readonly
https://www.googleapis.com/auth/chat.memberships.readonlyAdd this one too if you set directory_subject (recommended, see below):
https://www.googleapis.com/auth/admin.directory.user.readonlycurl -sS -X POST https://your-corveil-host/api/credentials \
-H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
-d @- <<'JSON'
{ "type": "google_service_account_json", "name": "acme-workspace-sa",
"secret": "{\"type\":\"service_account\", ... }" }
JSONIf you already created this credential for Meet, Drive, Calendar, or Gmail, reuse it. Add the Chat scopes to the existing DWD grant in the Workspace Admin console. Don't mint a second key.
Config fields
All config fields are optional. With a DWD service account and no config, the source fans out across every org member and ingests named spaces.
| Key | Notes |
|---|---|
directory_subject | Recommended. A Workspace user to impersonate for Admin SDK Directory lookups. Under user authentication the Chat API identifies people only as users/{id}; the Directory turns that into a name and email. Without it, authors, mentions, and members keep only the account id, and no Person edges are created. Needs admin.directory.user.readonly on the grant, a domain-wide read of user records. |
impersonate_subject | Connection-test user only. Test connection impersonates this user to probe Chat. Not needed for ingestion. |
member_allowlist | Comma-separated Workspace users. Restricts fan-out to these members (staged rollout / testing). |
workspace_domains | Comma-separated domains. Only members whose email domain matches are impersonated. |
space_types | Comma-separated: SPACE, GROUP_CHAT, DIRECT_MESSAGE. Defaults to SPACE. Add the other two only if your policy allows ingesting private conversations. |
spaces | Comma-separated space names (spaces/AAAA…) or ids. When set, only these spaces are ingested. The id is the last part of the space's URL in Chat. |
include_app_messages | true to ingest messages posted by Chat apps. Defaults to false. |
lookback_days | How far back the first poll reads a newly discovered space. Defaults to 7, at most 365. Older history is the worker's one-shot backfill (SENSOR_BACKFILL_SINCE). |
Provider-side setup
You need a Google Cloud project you own and a Workspace super admin.
- Service account. Reuse the one already used for Meet / Drive / Calendar / Gmail, or create one in IAM & Admin → Service Accounts and download its key JSON. Note its Client ID (the numeric OAuth 2 client id on the service account's details page).
- Enable the Google Chat API on the project (APIs & Services → Library → Google Chat API → Enable). If you set
directory_subject, also enable the Admin SDK API. - Configure the Chat app. The Chat API refuses every call, even user-authenticated reads, until the project has a Chat app configuration. Open Google Chat API → Configuration, give it a name (for example "Corveil"), an avatar URL, and a description. Turn off "Enable interactive features"; Corveil never posts or receives events. Under Visibility, the app doesn't need to be available to anyone.
- Authorize domain-wide delegation. In the Workspace Admin console go to Security → Access and data control → API controls → Manage Domain Wide Delegation. Edit the service account's client (or Add new with its Client ID) and add the three
chat.*.readonlyscopes, plusadmin.directory.user.readonlyif you'll setdirectory_subject. Keep the scopes the client already has for Meet, Drive, Calendar, or Gmail. - Make sure Google Chat is on for the organizational units whose members you want covered (Apps → Google Workspace → Google Chat). A member whose OU has Chat off is skipped with a warning.
Delegation changes can take a few minutes, and occasionally up to 24 hours, to reach every Google endpoint.
Wire-up
POST /api/sensors
{
"sensor_type": "googlechat",
"name": "Acme Google Chat",
"config": { "directory_subject": "admin@acme.com" },
"credentials": { "service_account_json": "<google_service_account_json credential id>" }
}Staged rollout to two users and one space first:
{
"sensor_type": "googlechat",
"name": "Acme Google Chat (pilot)",
"config": {
"member_allowlist": "alice@acme.com, bob@acme.com",
"spaces": "spaces/AAAAbCdEfGh",
"directory_subject": "admin@acme.com"
},
"credentials": { "service_account_json": "<credential id>" }
}Verify
Run Test connection. It impersonates impersonate_subject and lists one space, then, if directory_subject is set, reads that user's own Directory record. A missing scope or an unconfigured Chat app fails as an authorization error that names the user and Google's reason. With neither subject set it only checks that the key parses.
Once polling, each space appears as a Channel and its recent messages as Message entities. With directory_subject set, authors, mentions, and members canonicalize onto the Person entities your other sources already created.
How incremental sync works
Each poll:
- Lists the spaces every fanned-out member can see and dedups them by name. A member who can't be impersonated (a consumer account, a suspended user, Chat off for their OU) is skipped with a warning. If every member fails, the poll fails as an authorization error.
- Re-reads a space's memberships when it's new and then once a day. A Channel event is emitted only when the space's name, description, or members changed.
- Skips a space whose
lastActiveTimeisn't past its watermark. Otherwise it lists that space's messages withcreateTimeafter the watermark, oldest first, and advances the watermark as it reads.
The cursor keeps one watermark per space, so a poll that stops early resumes exactly where it stopped. One poll reads at most 200 pages (up to 200,000 messages).
Rate limits. Chat's read quota is per Google Cloud project: 3,000 message list calls a minute, and the same for space and member lists. The sensor paces itself to stay well under that. It retries a 429 or 503 up to three times, honoring Retry-After. If the quota won't refill within a minute, the poll stops early, keeps what it read, and the next poll continues.
Known limits
- Edits. Chat can only filter messages by creation time, so an edit to a message after it was ingested isn't picked up by incremental polls. The one-shot backfill re-reads the window and does pick up the edited text.
- Without
directory_subject, no Person edges. The Chat API returns users as bare ids under user authentication. Messages and spaces still ingest, but authors, mentions, and members can't be linked to People. - External users. Members from outside your Workspace aren't in your Directory, so they stay as account ids.
- Attachments are listed by file name in the message content. File contents aren't downloaded.
- Membership changes reach the graph within a day, not on the next poll.
- Coverage is members' coverage. A space no Corveil org member belongs to isn't visible to the sensor.