---
name: google-meet-meeting-bot-app
description: Sets up and registers the Google Cloud pieces behind Unified.to's Google Meet (meeting bot) integration (`googlemeetmeetingbot`) end to end — your own Google OAuth client (Unified has no shared credentials), the Cloud project that must also hold the Pub/Sub topic, the Meet REST, Google Workspace Events and Cloud Pub/Sub APIs, the sensitive `meetings.space.readonly` scope and its verification, a service account Unified uses to administer Pub/Sub in that project (project ID, service-account email and private key entered in the integration's settings fields in Unified), and an end-to-end test from `READY` through `IN_CALL_NOT_RECORDING` / `IN_CALL_RECORDING` to `CALL_ENDED` and `RECORDING_READY`. Use when asked to set up Google Meet meeting bots or a Meet notetaker, register a Google app for Unified's Meet bot, or fix a Meet bot that stays `READY` or never gets events. Read `google-cloud-console-oauth` and `google-meet-oauth-app` for the shared Google console and Meet API mechanics, and `unified-meeting-bots` for creating bots and handling events.
---

# Google Meet (meeting bot) App Setup and Registration

Get a working **Google Meet (meeting bot)** integration (`googlemeetmeetingbot`): your own Google OAuth client, a
Cloud project with the right APIs, and a service account Unified uses to run Pub/Sub in that project, all set on the
integration in Unified, so `calendar_bot`s follow Meet meetings.

Three things cost more than they look:

- **The topic lives in the OAuth client's project.** Google requires the Pub/Sub topic to be in the **same Cloud
  project as the OAuth client** that creates the Workspace Events subscription. So the OAuth client, the APIs and the
  topic live in **your** project (the integration has no Unified OAuth credentials,
  `has_unified_oauth2_credentials: false`). The service account can live anywhere, as long as it has the Pub/Sub
  permissions in that project.
- **Only meetings the connected user owns.** Each connection gets one Workspace Events subscription on the connected
  user, which only yields events for meeting spaces that user **owns** (normally the Calendar organizer). A bot for
  someone else's meeting is created but **stays `READY`**.
- **It's a monitor, not a bot in the call.** No participant joins and no media is captured. Recordings and transcripts
  exist only if someone starts them in the meeting, or the organizer has auto-recording / auto-transcripts on. There
  is no live or partial transcript, no chat, and no `DONE` status.

## Built on

- **`google-cloud-console-oauth`** — creating the project and the Web OAuth client, the consent screen, publishing
  status, sensitive-scope verification, and service accounts vs OAuth clients. Read first.
- **`google-meet-oauth-app`** — the Meet scope tiers, the Meet REST API surface (spaces, conference records,
  recordings, transcripts), the Workspace editions and Drive conditions for an artifact to exist, and Workspace Events
  subscriptions.
- **`unified-meeting-bots`** — creating bots, the `calendar_botevent` webhook, statuses and the `501` matrix.

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **Google Cloud project** | Holds the OAuth client, the APIs and the topic (§1) |
| **Who can create a service account and key** | And grant it Pub/Sub permissions on the project (§4) |
| **Org policy on service-account keys** | Some organizations block key creation; Unified needs the private key (§4) |
| **Unified region** | Decides the redirect URI (§2) |
| **External users?** | `meetings.space.readonly` is sensitive, so Google app verification before external users (§3) |
| **Which Unified permissions** | Bot permissions; `calendar_event_read` (to list events), `calendar_recording_read` if needed (§3) |
| **Are connected users the organizers?** | Only owned meeting spaces produce events |
| **Customers' Workspace editions and Drive** | Decide whether recordings and transcripts exist at all (`google-meet-oauth-app` §4) |

## Quick Start

1. Pick or create the Cloud project (§1).
2. Create a **Web application** OAuth client with Unified's redirect URI; configure the consent screen (§2).
3. Enable the **Google Meet REST API**, **Google Workspace Events API** and **Cloud Pub/Sub API** (plus Calendar
   for `event_id`) (§1).
4. Add the scopes; plan verification for `meetings.space.readonly` (§3).
5. Create a service account with Pub/Sub admin permissions on the project, and a JSON key (§4).
6. In Unified, activate **Google Meet (meeting bot)** and fill in the client ID and secret, project ID, service-account
   email and private key (§5).
7. Create the `calendar_botevent` webhook — Unified then creates the Google resources (§6).
8. Connect a test user who organizes a meeting and run the end-to-end test (§7, §8).

## Product state (from unified-api PR #10641 — re-verify before trusting)

- Integration type `googlemeetmeetingbot`, a Workspace Events monitor ("Option A", as `webexmeetingbot`); not
  launched, and the PR author reports it **not live-tested** against a real tenant.
- `has_unified_oauth2_credentials: false`; the integration asks for three extra settings: the project ID, the
  service-account email and the service-account private key, each with its own field and instructions in Unified.
- Inherits Google Meet's `calendar_calendar`, `calendar_event`, `calendar_busy` and `calendar_recording`; their native
  webhooks go to the Google Meet handler unchanged.
- `join_at` and `recording_config` return `501`. `list_meeting_url` isn't advertised yet (#10632), though
  `?meeting_url=` is passed through.
- Workspace Events subscriptions have a **7-day** maximum TTL. Unified renews them an hour early by **deleting and
  recreating** the topic, push subscription and Workspace Events subscription; events in that gap are lost.
- Suspended Workspace Events subscriptions are only logged. Transcripts are read up to 50 pages × 100 entries.

If the integration or the Google console doesn't look like this, stop and report what you see.

## 1. The Cloud project and APIs

Use the OAuth client's project for the APIs and the topic (Google's same-project rule); this is the project ID you
enter in §5. Enable:

- **Google Meet REST API** — look up spaces, conference records, participants, recordings, transcripts.
- **Google Workspace Events API** — the subscription on the connected user.
- **Cloud Pub/Sub API** — the topic and push subscription Unified creates.
- **Google Calendar API** — if you create bots from a `calendar_event` (`event_id`) or use the inherited calendar
  objects.

Do **not** enable or plan around the Meet Media API: Google deprecated it on 2026-10-28 and its endpoints stop on
2027-01-15 (per PR #10641; see `google-meet-oauth-app` §5 for what it was).

## 2. The OAuth client

1. **APIs & Services → Credentials → Create credentials → OAuth client ID → Web application**
   (`google-cloud-console-oauth`).
2. Authorized redirect URI for your Unified region:
   - US `https://api.unified.to/oauth/code`
   - EU `https://api-eu.unified.to/oauth/code`
   - AU `https://api-au.unified.to/oauth/code`
3. Copy the **client ID** and **client secret**.

## 3. Scopes

Sign-in always adds `openid`, `profile` and `email` (the OpenID `sub` builds the Workspace Events user target).

| Unified permission | Google scopes |
| --- | --- |
| `calendar_bot_read` | `meetings.space.readonly` |
| `calendar_bot_write` | `meetings.space.readonly`, `calendar.events.readonly` |
| `calendar_botevent_read` | `openid`, `meetings.space.readonly` |
| `calendar_recording_read` | `meetings.space.readonly` |
| `calendar_event_read` | `calendar.events.readonly` |

Scope URLs are `https://www.googleapis.com/auth/<scope>`. Creating a bot from an `event_id` needs only
`calendar_bot_write`; `calendar_event_read` is for listing events. Other inherited calendar permissions use the
scopes the Google Meet integration already maps; select only what you need.

`meetings.space.readonly` is **sensitive**: an app used by external users needs Google's app verification (no CASA
assessment, which is for restricted scopes). Plan the timeline — see `google-cloud-console-oauth`.

## 4. The service account (Unified administers Pub/Sub with it)

1. **IAM & Admin → Service accounts → Create service account** (any project).
2. On the OAuth client's project (the one whose ID you enter in §5), grant it: topic get / create / delete, topic
   getIamPolicy / setIamPolicy, and subscription create / delete. The least built-in role is **Pub/Sub Admin**
   (`roles/pubsub.admin`) — Pub/Sub Editor lacks `setIamPolicy` — or use a custom role with exactly those
   permissions. Unified's token for it uses the `cloud-platform` scope.
3. **Keys → Add key → JSON**. From the key file you need `client_email` and `private_key` (including the BEGIN/END
   lines). Store the file safely; never commit it.

You don't create the topic, subscription or the publisher grant yourself — Unified does (§6). (Meet's push service
account, `meet-api-event-push@system.gserviceaccount.com`, needs Pub/Sub Publisher on the topic; PR #10641's test
plan expects Unified to create that IAM binding.)

## 5. Enter the credentials in Unified

1. `app.unified.to` → **Integrations** → activate **Google Meet (meeting bot)**.
2. Enter the OAuth **client ID** and **client secret**.
3. **Google Cloud Project ID (of this OAuth client)**: the project from §1.
4. **Service account email**: the key file's `client_email`. Like the PEM field, it appears after you enter the
   client ID, and Unified won't activate the integration until it's filled.
5. **OAuth 2 X.509 Certificate (.PEM)**: paste the key file's `private_key`, from `-----BEGIN PRIVATE KEY-----` to
   `-----END PRIVATE KEY-----`; the instructions under the field say the same.
6. Select permissions: `calendar_bot_read`, `calendar_bot_write`, `calendar_botevent_read`, plus
   `calendar_event_read` and `calendar_recording_read` as needed.

If any of the project ID, private key or service-account email is missing, creating the `calendar_botevent` webhook
returns `400`.

## 6. Webhook setup (what Unified creates in your project)

When you create a native `calendar_botevent` webhook on a connection, Unified creates in your project:

1. a Pub/Sub **topic**,
2. the **IAM binding** that lets Meet publish to it,
3. a **push subscription** to Unified,
4. a **Workspace Events subscription** on the connected user (`//cloudidentity.googleapis.com/users/{sub}`).

Every 7 days (an hour before expiry) Unified renews by deleting and recreating the topic, push subscription and
Workspace Events subscription; events in that gap are lost. Deleting the webhook removes all four. A second
`calendar_botevent` webhook on the connection returns `409`, and so does a webhook for a Google user who already has
an events subscription through another connection.

The push endpoint is `/webhook/googlemeetmeetingbot/{webhook_id}`. Deliveries carry no OIDC token: Unified acts on a
delivery only if its `ce-source` is this webhook's own subscription, and reads every resource back from the Meet API
with the connection's token.

## 7. How end users connect

End users authorize **Google Meet (meeting bot)** through the Authorization component or authorization link and
approve the scopes. Bots work for meetings **they own**. Until the app is verified, external users see the unverified
app screen and are capped by Google's testing limits (`google-cloud-console-oauth`).

## 8. Test end to end

1. Create the native `calendar_botevent` / `created` webhook. In the Cloud console, confirm the topic, IAM binding,
   push subscription and Workspace Events subscription exist.
2. Create a `calendar_bot` with a `https://meet.google.com/...` link (looked up by meeting code) or this connection's
   `calendar_event` id whose event has a Meet link (`conferenceData` or `hangoutLink`), for a meeting the connected
   user organizes → `READY` (or `IN_CALL_NOT_RECORDING` if the conference is already live). A bare Google event id
   is looked up on the `primary` calendar only.
3. Start the meeting → `BOT_STATUS_CHANGE` to `IN_CALL_NOT_RECORDING`; join and leave → `PARTICIPANT_JOINED` /
   `PARTICIPANT_LEFT`.
4. Start recording → `RECORDING_STARTED` and `IN_CALL_RECORDING`; stop → `RECORDING_STOPPED` and back to
   `IN_CALL_NOT_RECORDING`.
5. End the meeting → `CALL_ENDED`. Later, `RECORDING_READY` (the bot's `recording_id` is the **conference record id**)
   and one `TRANSCRIPT_RECEIVED` per transcript entry if transcription was on.
6. `GET /calendar/{connection_id}/recording/{recording_id}` on the same connection.
7. Delete the webhook and confirm the four Google resources are gone.

## Limits

- `501`: `join_at`, `recording_config`. `400`: not a Meet link. `404`: unknown Calendar event or meeting space.
  `422`: Calendar event without a Meet link. `409`: another unfinished bot for the exact same link (not
  `CALL_ENDED`, `DONE` or `FATAL`).
- Owned meetings only; others stay `READY`. No `DONE` — final status `CALL_ENDED`.
- No live or partial transcripts, chat, media or smart notes; `automatic_leave` unused.
- One bot follows **one conference**: the first conference in the space binds it, so a recurring meeting space needs a
  new bot per occurrence.
- Only `name` can be updated.

## Troubleshooting by error message

| Symptom | Cause and fix |
| --- | --- |
| Creating the webhook returns `400` | Project ID, private key or service-account email missing on the integration (§5) |
| Creating the webhook returns `409` | A `calendar_botevent` webhook already exists on this connection, or the Google user is subscribed through another connection |
| Webhook creation fails with a Pub/Sub or IAM permission error | Service account lacks the §4 permissions on the project (Pub/Sub Editor isn't enough) |
| Topic / subscription errors about the project | The project ID on the integration isn't the OAuth client's project (§1, §5) |
| `403 accessNotConfigured` / `SERVICE_DISABLED` | Meet REST, Workspace Events or Pub/Sub API not enabled (§1) |
| Bot stays `READY` | Connected user doesn't own the meeting space; webhook created after the meeting started; subscription suspended (only logged) or its renewal failed |
| Missing events around a 7-day mark | Renewal deletes and recreates the subscription; events in that gap are lost |
| Creating a bot returns `400` | Not a `meet.google.com` link |
| Creating a bot returns `404` | Unknown Calendar event or meeting space; a bare event id that isn't on the `primary` calendar |
| Creating a bot returns `422` | The Calendar event has no Meet link |
| Creating a bot returns `501` | Remove `join_at` / `recording_config` |
| No `RECORDING_READY` / transcript events | Nobody recorded or transcribed; edition or Drive conditions (`google-meet-oauth-app` §4) |
| Users see "Google hasn't verified this app" | `meetings.space.readonly` is sensitive — complete verification |
| Service-account key creation blocked | Org policy `iam.disableServiceAccountKeyCreation` — an org admin decides |

## Stop and ask

- Nobody can grant the service account a Pub/Sub role, or service-account keys are blocked by org policy.
- The product needs bots on meetings the connected user doesn't own — not supported (per-space subscriptions are a
  follow-up in PR #10641).
- The product needs live captions, chat or media from Meet — not available.
- The app has external users and verification hasn't started.

## References

- Google Workspace Events API — Meet events: https://developers.google.com/workspace/events/guides/events-meet
- Google Meet REST API: https://developers.google.com/workspace/meet/api/guides/overview
- Pub/Sub access control: https://cloud.google.com/pubsub/docs/access-control
- Meet release notes (Media API deprecation): https://developers.google.com/workspace/meet/release-notes
- `calendar_bot` model: https://docs.unified.to/calendar/bot/model
