{
  "openapi": "3.1.0",
  "info": {
    "title": "Webhooks",
    "description": "The Roam Webhooks API delivers real-time notifications to your application via webhooks. Subscribe to events and receive HTTP callbacks when things happen in your Roam workspace.\n\n**OpenAPI Spec:** [webhooks-v1.json](https://developer.ro.am/webhooks-v1.json)\n\nSee the [Migration Guide](/docs/guides/migration-v0-to-v1) for details on upgrading from v0.\n\nChat actor IDs use the shared [Identity & Principals](/docs/guides/identity-and-principals)\ncontract. Message and reaction events carry principal types inline so receivers\ncan prevent bot loops without another lookup.\n\n## Configuring Webhooks\n\nYou can configure webhooks in two ways:\n- **Static:** In **Roam Administration > Developer > API Client**, add webhook URLs directly to your app configuration\n- **Dynamic:** Use the subscription endpoints below to manage webhooks programmatically\n\n## Subscription Endpoints\n\n| Endpoint | Method | Description |\n|----------|--------|-------------|\n| [`/webhook.list`](/docs/webhooks/webhook-list) | GET | List webhook subscriptions |\n| [`/webhook.subscribe`](/docs/webhooks/webhook-subscribe) | POST | Create or update a webhook subscription |\n| [`/webhook.unsubscribe`](/docs/webhooks/webhook-unsubscribe) | POST | Remove a webhook subscription |\n| [`/webhook.deliveries`](/docs/webhooks/webhook-deliveries) | GET | List failed deliveries for debugging and replay |\n\n## Available Events\n\n| Event | Description |\n|-------|-------------|\n| [`chat.message`](/docs/webhooks/chat-message) | Chat message created, edited, or deleted (DM, channel, or mention) |\n| [`chat.reaction`](/docs/webhooks/chat-reaction) | A reaction was added to or removed from a message (one event per change) |\n| [`chat.link.shared`](/docs/webhooks/chat-link-shared) | A new message contains a link matching the app's registered domains |\n| [`lobby.booked`](/docs/webhooks/lobby-booked) | A lobby meeting was booked |\n| [`magicast.created`](/docs/webhooks/magicast-created) | A magicast has been created |\n| [`meeting.started`](/docs/webhooks/meeting-started) | A meeting has started |\n| [`meeting.ended`](/docs/webhooks/meeting-ended) | A meeting has ended; transcript and summary are ready |\n| [`user.status.update`](/docs/webhooks/user-status-update) | A user checked in or out |\n| [`onair.event.created`](/docs/webhooks/onair-event-created) | An On-Air event was created |\n| [`onair.event.updated`](/docs/webhooks/onair-event-updated) | An On-Air event was updated |\n| [`onair.event.canceled`](/docs/webhooks/onair-event-canceled) | An On-Air event was canceled |\n| [`onair.guest.rsvp`](/docs/webhooks/onair-guest-rsvp) | A guest's RSVP status changed |\n| [`onair.guest.added`](/docs/webhooks/onair-guest-added) | One or more guests were added to an event |\n| [`token.revoked`](/docs/webhooks/token-revoked) | An OAuth token for your app was revoked |\n| [`app.uninstalled`](/docs/webhooks/app-uninstalled) | Your app was uninstalled from a Roam |\n\n**v1 event names are dotted** (`chat.message`, `lobby.booked`). Colon names\n(`chat:message:dm`, `lobby:booked`) are the v0 catalog — sending them to\n`/v1/webhook.subscribe` returns `400` / `Unrecognized event`. The mapping\nis in the [Migration Guide](/docs/guides/migration-v0-to-v1#event-names).\n\n## Event Envelope\n\nEvery delivery body is a common envelope; the event-specific payload is\nnested under `data`:\n\n```json\n{\n  \"type\": \"chat.message\",\n  \"eventId\": \"0197f9a1-7d2e-7cc3-9f6a-8b1c2d3e4f5a\",\n  \"timestamp\": \"2026-07-07T18:23:45.123456Z\",\n  \"apiVersion\": \"2026-07-07\",\n  \"data\": { \"...\": \"the event payload\" }\n}\n```\n\n| Field | Description |\n|-------|-------------|\n| `type` | The full event name (matches the subscription's `event` and the `Roam-Event-Type` header) |\n| `eventId` | Unique ID of the event occurrence — identical across retries, so de-duplicate on it |\n| `timestamp` | When the event occurred, RFC3339 UTC with microsecond precision |\n| `apiVersion` | The [API version](/docs/guides/api-versioning) this body is rendered as; equals the `Roam-Version` header |\n| `data` | The event payload — see each event's page for its shape |\n\nOne handler can therefore verify the signature, de-dupe on `eventId`,\nand route on `type` before any event-specific parsing.\n\nPin `apiVersion` on subscribe (e.g. `\"2026-08-20\"`) if you want the\nenvelope regardless of the credential's default. Subscriptions on\n`2026-06-01` still receive the **bare** payload (`type` is a short\ndiscriminator such as `\"message\"`, fields at the top level).\n\n### Dual bodies for one event\n\nA single event can currently produce **two POSTs** to the same URL with\nthe same `webhook-id` and different JSON bodies:\n\n1. **Legacy tagged-id body** — `type` is a short discriminator\n   (`\"message\"`), IDs carry prefixes (`sender: \"B-…\"`, `chat: \"D-…\"`).\n   This is the v0 shape.\n2. **v1 body** — untagged UUIDs (`userId`, `chatId`, `userType`). On\n   pins `2026-07-07` and later this is the envelope above. On the\n   `2026-06-01` baseline it is bare (`type: \"message\"`, `version`,\n   fields at the top level).\n\nTreat them as one event: de-duplicate on `webhook-id`. Prefer the v1\nbody (`userId` / `chatId` present, or envelope `type` equal to the\nsubscribed event name such as `chat.message`). Do not process both.\n\n## Event Filters\n\nThe `chat.message` event supports filters to limit notifications:\n\n| Filter | Type | Description |\n|--------|------|-------------|\n| `chatType` | `\"dm\"` \\| `\"group\"` | Only receive DMs or group messages |\n| `mention` | `boolean` | Only receive messages that @mention your app |\n\nThe `chat.reaction` event supports:\n\n| Filter | Type | Description |\n|--------|------|-------------|\n| `names` | `string[]` | Only receive events where the changed reaction is one of these names |\n\nThe `meeting.ended` event supports:\n\n| Filter | Type | Description |\n|--------|------|-------------|\n| `hasVideo` | `true` | Only receive meetings that were video recorded. Keys on \"was recorded\", not \"the recording is ready to fetch\" — a matching delivery normally arrives while the upload is still in flight. `false` is rejected at subscribe time — omit the filter to receive all `meeting.ended` events. |\n\n**Filter examples:**\n- DM messages only: `{\"chatType\": \"dm\"}`\n- Mentions only: `{\"mention\": true}`\n- Group mentions: `{\"mention\": true, \"chatType\": \"group\"}`\n- Only ✅ reactions: `{\"names\": [\"white_check_mark\"]}`\n- Meetings that were video recorded: `{\"hasVideo\": true}`\n\n## Webhook Delivery Headers\n\nEach webhook delivery includes these headers:\n\n| Header | Description |\n|--------|-------------|\n| `Content-Type` | `application/json` |\n| `Roam-Event-Type` | Event name (e.g., `chat.message`); also in the body as `type` |\n| `Roam-Version` | The API version the body is rendered as; also in the body as `apiVersion` |\n| `webhook-id` | The event's `eventId`. **Identical on every attempt of the same event** — this is your de-duplication key |\n| `webhook-timestamp` | Unix timestamp when sent (only on signed deliveries) |\n| `webhook-signature` | Standard Webhooks signature (only when a signing secret is configured) |\n| `Roam-Retry-Num` | `1`, `2`, or `3` — which retry this is. Absent on the first attempt, so its presence alone means \"redelivery\" |\n| `Roam-Retry-Reason` | Why the *previous* attempt failed: `transport` (connection error or timeout), `http_5xx`, or `http_429`. Sent alongside `Roam-Retry-Num` |\n\n## Signature Verification\n\nWebhooks are signed using the [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.\n\nYour **Webhook Signing Secret** is available in **Roam Administration > Developer > API Client**.\n\nTo verify a webhook:\n1. Concatenate: `{webhook-id}.{webhook-timestamp}.{payload}`\n2. Compute HMAC-SHA256 using your signing secret (base64-decoded)\n3. Compare with the signature in `webhook-signature` header\n\nWe recommend using the [standard-webhooks client libraries](https://github.com/standard-webhooks/standard-webhooks#libraries).\nVerify over the **raw** request body — parsing and re-serializing the JSON\nfirst will change the bytes and break the signature.\n\n### Node.js / Express\n\n```javascript\nimport express from \"express\";\nimport { Webhook } from \"standardwebhooks\";\n\nconst app = express();\nconst webhook = new Webhook(process.env.ROAM_WEBHOOK_SIGNING_SECRET);\n\napp.post(\"/webhooks/roam\", express.raw({ type: \"application/json\" }), (req, res) => {\n  let payload;\n  try {\n    payload = webhook.verify(req.body.toString(\"utf8\"), {\n      \"webhook-id\": req.get(\"webhook-id\"),\n      \"webhook-timestamp\": req.get(\"webhook-timestamp\"),\n      \"webhook-signature\": req.get(\"webhook-signature\"),\n    });\n  } catch {\n    return res.sendStatus(401);\n  }\n\n  // Acknowledge quickly; enqueue payloads for background processing.\n  res.sendStatus(200);\n});\n```\n\n### Python / Flask\n\n```python\nimport os\nfrom flask import Flask, abort, request\nfrom standardwebhooks.webhooks import Webhook\n\napp = Flask(__name__)\nwebhook = Webhook(os.environ[\"ROAM_WEBHOOK_SIGNING_SECRET\"])\n\n@app.post(\"/webhooks/roam\")\ndef receive_roam_webhook():\n    try:\n        payload = webhook.verify(request.get_data(), dict(request.headers))\n    except Exception:\n        abort(401)\n\n    # Acknowledge quickly; enqueue payloads for background processing.\n    return \"\", 200\n```\n\n## Delivery Behavior\n\n- **Timeout**: Each attempt must return a 2xx within **3 seconds**. A slower\n  response counts as a failed attempt even if your handler eventually\n  succeeds, so acknowledge first and do the work in the background.\n- **Retries**: Transient failures (connection errors, timeouts, `429`, `5xx`)\n  get **three retries after the initial attempt**: near-immediate (about a\n  second), then about **+1 minute**, then about **+5 minutes** — roughly a\n  six-minute window end to end. This deliberately mirrors Slack's Events API\n  ladder, so a handler ported from Slack needs no new retry assumptions.\n  Other non-2xx responses are not retried. A `410 Gone` response deletes the\n  subscription.\n- **`Retry-After`**: On a `429` or `503`, a `Retry-After` header is honored in\n  place of the ladder's next wait, capped at 5 minutes.\n- **De-duplication**: `webhook-id` (header) is **identical on every attempt of\n  the same event** and equals the body's `eventId` — it is the de-duplication\n  key. Retries additionally carry `Roam-Retry-Num` and `Roam-Retry-Reason`;\n  the first attempt carries neither. Treat deliveries as at-least-once and\n  key your idempotency on `webhook-id`.\n- **Retries are not durable**: pending retries are held in memory only. A Roam\n  process restart drops them, and a bounded pending-retry set sheds ladders\n  under extreme load. If your endpoint is unreachable for longer than the\n  ~6-minute window, those events are lost — this is best-effort at-least-once\n  delivery, not a durable outbox.\n- **Order**: Webhooks are delivered asynchronously and may arrive out of order.\n  Retries widen that window: a retried event can land minutes after events that\n  occurred later.\n\nFor reliable processing, we recommend:\n- Acknowledge webhooks immediately with a 200 response, within 3 seconds\n- Process webhook data asynchronously in a background job\n- Use `webhook-id` / `eventId` for idempotency\n- Reconcile with [`/webhook.deliveries`](/docs/webhooks/webhook-deliveries) if\n  you were down longer than the retry window\n\n### Subscription health\n\nRepeated **terminal** failures pause a destination rather than retrying\nevery later event forever. Terminal means the retry ladder finished (or\nthe response was not retryable — `4xx` other than `429`).\n\n| After | What happens |\n|-------|--------------|\n| 24 hours of consecutive terminal failures | Subscription is **paused** (`disabledAt` is set). While paused, Roam sends **one real event per day**. A 2xx, or [`/webhook.subscribe`](/docs/webhooks/webhook-subscribe) to the same event+URL, turns it back on. |\n| 14 days of the same fail streak | **Dynamic** subscriptions (`dynamic: true`, created via subscribe) are **deleted**. Static Developer Settings URLs stay paused until they succeed or you save the config. |\n| Destination returns `410 Gone` | Subscription is **deleted** immediately (static or dynamic). |\n\nA gap longer than 24 hours since the last failure starts a new streak —\na sparse destination (for example a weekly `meeting.ended`) is not paused\nafter two far-apart failures. There is no minimum failure count.\n\n[`/webhook.list`](/docs/webhooks/webhook-list) and\n[`/webhook.subscribe`](/docs/webhooks/webhook-subscribe) return these\nfields on the webhook object (each omitted when null):\n\n| Field | Present when |\n|-------|--------------|\n| `lastSuccessAt` | The destination has succeeded at least once (kept across a pause/resume) |\n| `failStreakStartedAt` | The current consecutive-failure span is open |\n| `disabledAt` | The subscription is paused |\n\n### Debugging failed deliveries\n\nEvery failed delivery (timeouts included) is recorded and queryable for\n~30 days via [`/webhook.deliveries`](/docs/webhooks/webhook-deliveries) —\nthe outcome, status code, and (for HTTP errors) a truncated copy of your\nserver's response body. If your endpoint was down, use it to find what you\nmissed and re-fetch the affected resources to replay them.\n\n## Authentication\n\n```\nAuthorization: Bearer YOUR_TOKEN\n```\n\n## Errors\n\nThe subscription endpoints return a JSON body with an `error` message.\n`401` means missing or invalid credentials; `403` means the token is valid\nbut lacks the required scope (e.g. `webhook:write`).\n\n## Base URL\n\n```\nhttps://api.ro.am/v1\n```\n\n---\nHave questions? Contact us via [Team Roam Support Chat](https://ro.am/support/contact-us).\n",
    "version": "1.0",
    "termsOfService": "https://ro.am/terms",
    "contact": {
      "name": "Team Roam Support Chat",
      "url": "https://ro.am/support/contact-us"
    }
  },
  "servers": [
    {
      "url": "https://api.ro.am/v1",
      "description": "Production Server"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "externalDocs": {
    "description": "Webhooks API Documentation",
    "url": "https://developer.ro.am/docs/webhooks"
  },
  "paths": {
    "/webhook.list": {
      "get": {
        "summary": "List webhook subscriptions",
        "description": "List all webhook subscriptions owned by the authenticated API client.\n\nThe response includes both **dynamic** subscriptions (created via\n[`/webhook.subscribe`](/docs/webhooks/webhook-subscribe)) and **static**\nsubscriptions configured in the Roam Administration UI.\n\nEach object may include `lastSuccessAt`, `failStreakStartedAt`, and\n`disabledAt` (omitted when null). `disabledAt` means the destination is\npaused. See [Subscription health](/docs/webhooks/webhooks#subscription-health).\n\n**Required scope:** `webhook:read`\n\n---\n\n**OpenAPI Spec:** [webhooks-v1.json](https://developer.ro.am/webhooks-v1.json)\n",
        "operationId": "webhook.list",
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of webhook subscriptions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "description": "Webhook subscriptions owned by this API client. Empty array if none exist.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Unique identifier of the webhook subscription."
                          },
                          "event": {
                            "type": "string",
                            "description": "Subscribed event name (e.g. `chat.message`, `chat.reaction`)."
                          },
                          "url": {
                            "type": "string",
                            "format": "uri",
                            "description": "Destination URL for webhook deliveries."
                          },
                          "filter": {
                            "$ref": "#/components/schemas/WebhookSubscriptionFilter",
                            "description": "Event-specific filter applied to the subscription. Omitted if no filter is set."
                          },
                          "dynamic": {
                            "type": "boolean",
                            "description": "`true` if the subscription was created via `/webhook.subscribe`.\n`false` if it was configured statically in the Roam Administration UI.\n"
                          },
                          "created": {
                            "type": "string",
                            "format": "date-time",
                            "description": "When the subscription was created."
                          },
                          "lastSuccessAt": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Last terminal 2xx (RFC3339 UTC). Omitted until the\ndestination has succeeded at least once.\n"
                          },
                          "failStreakStartedAt": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Start of the current consecutive-failure span\n(RFC3339 UTC). Omitted when healthy.\n"
                          },
                          "disabledAt": {
                            "type": "string",
                            "format": "date-time",
                            "description": "When the fail streak reached 24 hours (RFC3339 UTC).\nWhile set the subscription is paused. Omitted when\nactive.\n"
                          }
                        },
                        "required": [
                          "id",
                          "event",
                          "url",
                          "dynamic"
                        ]
                      }
                    }
                  },
                  "required": [
                    "webhooks"
                  ]
                },
                "example": {
                  "webhooks": [
                    {
                      "id": "19c6401f-6d02-4d8c-87c5-9fc45f02f4b5",
                      "event": "chat.message",
                      "url": "https://example.com/hooks/messages",
                      "filter": {
                        "mention": true
                      },
                      "dynamic": true,
                      "created": "2026-03-12T17:42:18Z",
                      "lastSuccessAt": "2026-08-17T14:02:11Z"
                    },
                    {
                      "id": "a7f8e3d2-1b5c-4e9f-8d2a-6f0b1c3d4e5f",
                      "event": "chat.reaction",
                      "url": "https://example.com/hooks/reactions",
                      "dynamic": false,
                      "created": "2026-01-04T09:13:00Z",
                      "lastSuccessAt": "2026-08-15T09:00:00Z",
                      "failStreakStartedAt": "2026-08-16T09:00:00Z",
                      "disabledAt": "2026-08-17T09:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized. Either the credentials are invalid, or the token is\nmissing the required `webhook:read` scope.\n",
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "description": "An internal error occurred.",
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/webhook.subscribe": {
      "post": {
        "summary": "Subscribe to an event webhook",
        "description": "Create or update a webhook subscription for a given event. If a subscription\nalready exists for the same event and URL, its filter is updated instead of\ncreating a duplicate. Re-subscribing the same event and URL also clears a\npause (`disabledAt` / `failStreakStartedAt`) so deliveries resume on the\nnext event. See [Subscription health](/docs/webhooks/webhooks#subscription-health).\n\n**Event names are dotted:** `chat.message`, `lobby.booked`,\n`magicast.created`. Colon names (`chat:message:dm`, `lobby:booked`) are\nv0-only — sending them here returns `400` / `Unrecognized event`.\n\nRoam does not probe the destination URL when you subscribe — the\nsubscription is created immediately and the first delivery is a real event.\n\nSee the [Webhooks overview](/docs/webhooks/webhooks) for the full list of event names and their filters.\n\n**Required scope:** `webhook:write`\n\n---\n\n**OpenAPI Spec:** [webhooks-v1.json](https://developer.ro.am/webhooks-v1.json)\n",
        "operationId": "webhook.subscribe",
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookSubscriptionRequest"
              },
              "examples": {
                "chat-message": {
                  "summary": "Subscribe to chat message events",
                  "value": {
                    "url": "https://example.com/hooks/messages",
                    "event": "chat.message",
                    "filter": {
                      "mention": true
                    }
                  }
                },
                "chat-message-group": {
                  "summary": "Subscribe to group messages only",
                  "value": {
                    "url": "https://example.com/hooks/messages",
                    "event": "chat.message",
                    "filter": {
                      "chatType": "group"
                    }
                  }
                },
                "chat-reaction": {
                  "summary": "Subscribe to reaction events",
                  "value": {
                    "url": "https://example.com/hooks/reactions",
                    "event": "chat.reaction"
                  }
                },
                "chat-reaction-filtered": {
                  "summary": "Subscribe to ✅ reactions only",
                  "value": {
                    "url": "https://example.com/hooks/reactions",
                    "event": "chat.reaction",
                    "filter": {
                      "names": [
                        "white_check_mark"
                      ]
                    }
                  }
                },
                "meeting-ended-has-video": {
                  "summary": "Subscribe to meeting.ended only when video was recorded",
                  "value": {
                    "url": "https://example.com/hooks/meetings",
                    "event": "meeting.ended",
                    "filter": {
                      "hasVideo": true
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription created or updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                },
                "example": {
                  "id": "19c6401f-6d02-4d8c-87c5-9fc45f02f4b5",
                  "event": "chat.message",
                  "url": "https://example.com/hooks/messages",
                  "filter": {
                    "mention": true
                  },
                  "dynamic": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request. Common causes:\n- `url` missing, malformed, or not HTTPS\n- `url` is not a valid webhook destination — private, internal, or\n  otherwise undeliverable hosts are rejected at subscribe time\n- `event` missing or unrecognized\n- Invalid filter for the event\n",
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "description": "Presented invalid authentication credentials.",
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "description": "An internal error occurred.",
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/webhook.unsubscribe": {
      "post": {
        "summary": "Unsubscribe from an event webhook",
        "description": "Remove a webhook subscription by ID.\n\nThe request body is JSON: `{\"id\": \"<subscription uuid>\"}`. This differs\nfrom v0, which expects `application/x-www-form-urlencoded` with the same\n`id` field. Sending JSON to `/v0/webhook.unsubscribe` returns\n`id parameter required`.\n\n**Required scope:** `webhook:write`\n\n---\n\n**OpenAPI Spec:** [webhooks-v1.json](https://developer.ro.am/webhooks-v1.json)\n",
        "operationId": "webhook.unsubscribe",
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUnsubscribeRequest"
              },
              "example": {
                "id": "19c6401f-6d02-4d8c-87c5-9fc45f02f4b5"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Subscription deleted."
          },
          "400": {
            "description": "Bad request.",
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "description": "Presented invalid authentication credentials.",
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "description": "Subscription not found."
          },
          "500": {
            "description": "An internal error occurred.",
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/webhook.deliveries": {
      "get": {
        "summary": "List failed webhook deliveries",
        "description": "List recent **failed** webhook delivery attempts for the authenticated API\nclient, newest first. Use this to debug a misbehaving endpoint and to find\nthe events you need to replay: successful (2xx) deliveries are never\nrecorded, so every row here is a delivery your endpoint did not accept.\n\nTimeouts are first-class failures: `statusCode` is `0` and `error` is\n`timeout`. For HTTP error responses, a truncated copy of your server's\nresponse body is included to aid debugging. The request payload is never\nstored — to recover the data, re-fetch the underlying resource (e.g. via\n`chat.history`) using the delivery's `messageId`/`event` context.\n\nResults are strictly scoped to the caller's own subscriptions and retained\nfor roughly 30 days.\n\n**Access:** Organization and Personal.\n\n**Required scope:** `webhook:read`\n\n---\n\n**OpenAPI Spec:** [webhooks-v1.json](https://developer.ro.am/webhooks-v1.json)\n",
        "operationId": "webhook.deliveries",
        "security": [
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhook",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Only return deliveries for this webhook subscription ID."
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only return deliveries for this event name (e.g. `chat.message`)."
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only return deliveries after this time (RFC3339 or `YYYY-MM-DD`). Results switch to oldest-first."
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only return deliveries before this time (RFC3339 or `YYYY-MM-DD`)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "maximum": 100
            },
            "description": "Maximum number of deliveries to return."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination cursor from a previous response's `nextCursor`."
          }
        ],
        "responses": {
          "200": {
            "description": "Failed delivery attempts for the caller's subscriptions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deliveries": {
                      "type": "array",
                      "description": "Failed delivery attempts, newest first. Empty array if none.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "timestamp": {
                            "type": "string",
                            "format": "date-time",
                            "description": "When the delivery was attempted (RFC3339 UTC)."
                          },
                          "webhookId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "ID of the webhook subscription. Omitted for static (UI-configured) destinations."
                          },
                          "event": {
                            "type": "string",
                            "description": "The event name that was being delivered (e.g. `chat.message`)."
                          },
                          "url": {
                            "type": "string",
                            "format": "uri",
                            "description": "The destination URL that was called."
                          },
                          "statusCode": {
                            "type": "integer",
                            "description": "HTTP status code returned by the destination. `0` for connection errors and timeouts."
                          },
                          "error": {
                            "type": "string",
                            "description": "Failure classification (e.g. `timeout`, `http_4xx`, `http_5xx`, `connection`)."
                          },
                          "response": {
                            "type": "string",
                            "description": "Truncated response body from the destination, present for HTTP error statuses."
                          },
                          "durationMs": {
                            "type": "integer",
                            "description": "How long the delivery attempt took, in milliseconds."
                          },
                          "messageId": {
                            "type": "string",
                            "description": "ID of the message that triggered the event, when applicable."
                          },
                          "success": {
                            "type": "boolean",
                            "description": "Always `false` — only failed deliveries are recorded."
                          }
                        },
                        "required": [
                          "timestamp",
                          "event",
                          "url",
                          "statusCode",
                          "error",
                          "durationMs",
                          "success"
                        ]
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Opaque cursor for the next page. Omitted on the last page."
                    }
                  },
                  "required": [
                    "deliveries"
                  ]
                },
                "example": {
                  "deliveries": [
                    {
                      "timestamp": "2026-07-19T18:23:45Z",
                      "webhookId": "19c6401f-6d02-4d8c-87c5-9fc45f02f4b5",
                      "event": "chat.message",
                      "url": "https://example.com/hooks/messages",
                      "statusCode": 0,
                      "error": "timeout",
                      "durationMs": 3000,
                      "success": false
                    },
                    {
                      "timestamp": "2026-07-19T18:22:10Z",
                      "webhookId": "19c6401f-6d02-4d8c-87c5-9fc45f02f4b5",
                      "event": "chat.message",
                      "url": "https://example.com/hooks/messages",
                      "statusCode": 503,
                      "error": "http_5xx",
                      "response": "upstream unavailable",
                      "durationMs": 187,
                      "success": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter (e.g. malformed `webhook` UUID or `cursor`).",
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "description": "Presented invalid authentication credentials.",
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "description": "An internal error occurred.",
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "webhooks": {
    "chat.link.shared": {
      "post": {
        "summary": "Matching link shared",
        "description": "A newly created text or markdown message contains one or more links that\nmatch this app's registered unfurl domains.\n\n**Event name:** `chat.link.shared`\n\n**Required scope:** `links:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope).\nURLs are unique, ordered by first appearance, and filtered separately for\neach installation. Edits and messages sent by the same app installation do\nnot produce this event.\n\nThe app receives only messages in chats it can see: organization apps must\nbe chat members or the chat must be a public group; personal installations\nrequire their owner to be in the chat.\n",
        "operationId": "chat.link.shared",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "chat.link.shared"
                        ],
                        "description": "Discriminator — always `chat.link.shared` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/LinkSharedEventData"
                      }
                    },
                    "required": [
                      "data"
                    ]
                  }
                ]
              },
              "example": {
                "type": "chat.link.shared",
                "eventId": "0197f9a2-1a51-7aa1-8b2c-3d4e5f6a7b8c",
                "timestamp": "2026-07-20T15:30:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "chatId": "8f3b9c2e-1a4d-4e7b-9c0a-2b6d1f5e3a7c",
                  "chatType": "group",
                  "messageId": "16933dd4-6e4c-4acf-9af1-d1972f9b4d34",
                  "messageTimestamp": 1748906400000000,
                  "userId": "ad1e9cc0-0ffd-47e5-895c-2630a73327b4",
                  "links": [
                    {
                      "url": "https://status.example.com/incidents/123",
                      "domain": "status.example.com"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return 200 to acknowledge the delivery."
          }
        }
      }
    },
    "chat.message": {
      "post": {
        "summary": "Chat message received",
        "description": "A chat message addressed to the app — fired when a message is **created,\nedited, or deleted**.\n\n**Event name:** `chat.message`\n\n**Required scope:** `chat:history`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nDelivery follows **membership**:\n\n- **Organization:** every message in chats the app is a member of (groups\n  and DMs). @-mentioning the app does not add it to a group.\n- **Personal:** every message in chats the **owner** is a member of\n  (groups and DMs with other people), plus the `{bot, owner}` self-DM.\n  Personal bots cannot be added as group members.\n\nSubscribe with `{ \"mention\": true }` to receive only messages that\n@-mention the app.\n\n## Message lifecycle: create, edit, delete\n\nThe same `chat.message` event fires for all three operations — the\n`Roam-Event-Type` header is always `chat.message`. Dispatch on the event,\nthen read the `data.version` field (the message's edit revision) to tell\nthem apart:\n\n| Operation | How to recognize it | Payload |\n|-----------|---------------------|---------|\n| New message | `data.version` is `1` | Full content |\n| Edit | `data.version` > `1`, content present | The **new** content |\n| Delete | `data.contentType` is `\"deleted\"` (version bumped again) | Identity only, no content |\n\nA message keeps a stable identity of **`(chatId, timestamp)`** across its\nlifecycle, and `version` increases on every change (so it also orders\nevents). To mirror chat, upsert/remove by that key: a later event for the\nsame key with a **higher `version`** supersedes your local copy, and a\n`\"deleted\"` event removes it. The prior content of an edited or deleted\nmessage is not re-sent — you already received it on the original event.\n\nEdits and deletes are **v1-only**: the legacy v0 `chat:message:*` events\nremain create-only. A delete carries no mentions, so a `{ \"mention\": true }`\nfilter never matches a delete (key on the original message you received).\n\n**Adding an organization app to a group:**\n1. Open the group in Roam\n2. Click **Add Members**\n3. Search for your app by name\n4. Add the app to the group\n\nOr call [`group.join`](/docs/api/group-join) (self-join a public group) or\n[`group.add`](/docs/api/group-add). @-mention does not join. In a public\ngroup the Roam client may prompt **Invite {app} to this group?** when\nsomeone @-mentions a non-member org app; dismissing still sends the\nmessage and the app is not added.\n\nOnce added, the app receives every message in that group (unless you\nfilter). Thread replies use the same membership rule as top-level\nmessages.\n\n**Filter options:**\n- `chatType`: `\"dm\"` or `\"group\"` — Filter by chat type\n- `mention`: `true` — Only messages that @-mention the app\n\n`data.userType` is the principal type of `data.userId` (`user` or `bot`).\nIt always matches [`user.info`](/docs/api/user-info) under the same app\ncredentials, so check it to prevent bot-to-bot loops.\n\nMessages posted with a per-message sender display override carry it in the\noptional `data.sender` object (`name` / `imageUrl`); `data.userId` remains\nthe authoring identity. See the\n[Sender Profiles guide](/docs/guides/sender-profiles).\n\nPoll messages arrive with `contentType: \"poll\"` and a `poll` object\n(`question`, `options`, `allowMultipleAnswers`, `closesAt`) — the same\nshape [`/chat.history`](/docs/api/chat-history) returns. Vote tallies are\nnot on this event; call [`/reaction.list`](/docs/api/reaction-list)\n(`pollVotes`).\n\nThe field is additive on both supported webhook versions. Baseline\n(`2026-06-01`) delivers it in the bare payload; Latest nests the same fields\nunder `data`:\n\n```json\n{ \"userId\": \"b7c34e90-...\", \"userType\": \"bot\", \"contentType\": \"text\" }\n{ \"type\": \"chat.message\", \"data\": { \"userId\": \"b7c34e90-...\", \"userType\": \"bot\", \"contentType\": \"text\" } }\n```\n\nA malformed event whose author cannot be resolved as a visible principal is\nnot delivered with a missing type field.\n",
        "operationId": "chat.message",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "chat.message"
                        ],
                        "description": "Discriminator — always `chat.message` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "version": {
                            "type": "integer",
                            "description": "The message's edit revision: `1` for a newly-created message,\n`2`+ for an edit (the revision count), and bumped again on a\ndelete. Use it to order events for a `(chatId, timestamp)` key\nand to distinguish create / edit / delete (see the lifecycle\ntable above).\n"
                          },
                          "contentType": {
                            "type": "string",
                            "description": "Content type of the message: `text`, `voice`, `block`, or\n`poll`. A delete tombstone has `contentType: \"deleted\"`\nand carries no content fields.\n"
                          },
                          "userId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Principal ID of the message sender"
                          },
                          "userType": {
                            "type": "string",
                            "enum": [
                              "user",
                              "bot"
                            ],
                            "description": "Principal type of `userId`; equals `user.info.type` under the same credentials."
                          },
                          "sender": {
                            "type": "object",
                            "description": "Per-message sender display override supplied at send time.\nPresent only when the message carries one; `userId` remains\nthe authoring identity. See the\n[Sender Profiles guide](/docs/guides/sender-profiles).\n",
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "Display name override for this message."
                              },
                              "imageUrl": {
                                "type": "string",
                                "format": "uri",
                                "description": "Avatar URL override for this message."
                              }
                            }
                          },
                          "chatId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "ID of the chat where the message was sent"
                          },
                          "chatType": {
                            "type": "string",
                            "enum": [
                              "dm",
                              "group"
                            ],
                            "description": "Type of chat — `dm` (direct message) or `group` (group chat)"
                          },
                          "timestamp": {
                            "type": "integer",
                            "description": "Message key as Unix microseconds. Stable across the message's lifecycle."
                          },
                          "threadTimestamp": {
                            "type": "integer",
                            "description": "Unix microseconds timestamp of the parent message (thread replies only)"
                          },
                          "replyTimestamp": {
                            "type": "integer",
                            "description": "Timestamp of the message this one quotes (DM or channel-thread quoted replies)"
                          },
                          "text": {
                            "type": "string",
                            "description": "Text of the message, formatted as GitHub-flavored markdown. Absent on deletes and non-text messages. Mention tokens use Slack's syntax — `<@ID>` for principals (users and bots), `<!subteam^ID>` for groups and channels, `<!channel>` for the broadcast keyword."
                          },
                          "items": {
                            "type": "array",
                            "description": "File attachments (images, documents, etc.)",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "description": "UUID identifying this item"
                                },
                                "created": {
                                  "type": "string",
                                  "format": "date-time",
                                  "description": "When the item was created"
                                },
                                "type": {
                                  "type": "string",
                                  "enum": [
                                    "photo",
                                    "blob"
                                  ],
                                  "description": "Type of item: photo (images) or blob (other files)"
                                },
                                "mime": {
                                  "type": "string",
                                  "description": "MIME type of the file"
                                },
                                "name": {
                                  "type": "string",
                                  "description": "Filename"
                                },
                                "url": {
                                  "type": "string",
                                  "format": "uri",
                                  "description": "Download URL for the file"
                                },
                                "thumbnail": {
                                  "type": "string",
                                  "format": "uri",
                                  "description": "Thumbnail URL (images only)"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "File size in bytes"
                                }
                              },
                              "required": [
                                "id",
                                "type",
                                "name",
                                "url"
                              ]
                            }
                          },
                          "voice": {
                            "type": "object",
                            "description": "Voice message content, present when `contentType` is `voice`.",
                            "properties": {
                              "audioUrl": {
                                "type": "string",
                                "format": "uri",
                                "description": "URL of the voice audio."
                              },
                              "duration": {
                                "type": "integer",
                                "description": "Duration of the voice message in seconds."
                              },
                              "transcript": {
                                "type": "string",
                                "description": "Transcript of the voice message, if available."
                              }
                            }
                          },
                          "poll": {
                            "type": "object",
                            "description": "Poll content, present when `contentType` is `poll`.",
                            "properties": {
                              "question": {
                                "type": "string",
                                "description": "The poll question."
                              },
                              "options": {
                                "type": "array",
                                "description": "The poll answer options.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "id": {
                                      "type": "string",
                                      "description": "Unique option identifier."
                                    },
                                    "text": {
                                      "type": "string",
                                      "description": "Option display text."
                                    }
                                  }
                                }
                              },
                              "allowMultipleAnswers": {
                                "type": "boolean",
                                "description": "Whether voters can select multiple options."
                              },
                              "closesAt": {
                                "type": "string",
                                "format": "date-time",
                                "description": "When the poll closes (RFC3339 UTC). Omitted if no close time is set."
                              }
                            }
                          },
                          "blocks": {
                            "type": "array",
                            "description": "Block Kit blocks, present on block messages.",
                            "items": {
                              "type": "object"
                            }
                          },
                          "color": {
                            "type": "string",
                            "description": "Color strip for block messages (`good`, `warning`, `danger`, or a hex value)."
                          }
                        },
                        "required": [
                          "version",
                          "contentType",
                          "userId",
                          "userType",
                          "chatId",
                          "chatType",
                          "timestamp"
                        ]
                      }
                    }
                  }
                ]
              },
              "examples": {
                "new": {
                  "summary": "New message (version 1)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-7d2e-7cc3-9f6a-8b1c2d3e4f5a",
                    "timestamp": "2024-06-20T16:13:20.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 1,
                      "contentType": "text",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900000000000,
                      "text": "Deploying v2.3.0 now"
                    }
                  }
                },
                "poll": {
                  "summary": "Poll message (contentType poll)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-7d2e-7cc3-9f6a-8b1c2d3e4f5b",
                    "timestamp": "2024-06-20T16:15:00.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 1,
                      "contentType": "poll",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900100000000,
                      "poll": {
                        "question": "Favorite letter?",
                        "allowMultipleAnswers": true,
                        "closesAt": "2024-06-21T16:15:00Z",
                        "options": [
                          {
                            "id": "opt-a",
                            "text": "A"
                          },
                          {
                            "id": "opt-b",
                            "text": "B"
                          }
                        ]
                      }
                    }
                  }
                },
                "edit": {
                  "summary": "Edit (version 2, carries the new content)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-8e3f-7dd4-a07b-9c2d3e4f5a6b",
                    "timestamp": "2024-06-20T16:20:41.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 2,
                      "contentType": "text",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900000000000,
                      "text": "Deploying v2.3.1 now (hotfix included)"
                    }
                  }
                },
                "delete": {
                  "summary": "Delete (contentType deleted, identity only)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-9f40-7ee5-b18c-ad3e4f5a6b7c",
                    "timestamp": "2024-06-20T16:25:03.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 3,
                      "contentType": "deleted",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900000000000
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "chat.reaction": {
      "post": {
        "summary": "Reaction added or removed",
        "description": "A reaction was added to or removed from a chat message the app can see.\n\n**Event name:** `chat.reaction`\n\n**Required scope:** `chat:history`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nLike `chat.message`, this is delivered for any message in a chat the app\nparticipates in or can see (Slack `reaction_added` / `reaction_removed`\nparity) — not only the app's own messages. The reacted message's author is\nin `messageAuthorId`, so an app can filter to reactions on its own\nmessages.\n\n**One event is delivered per reaction change**: `action` says whether the\nreaction was added or removed, `name` is the single reaction that changed,\nand `userId` is the user who changed it. There is no batching or\ndebouncing — an add followed by a remove delivers two events.\n\nDelivery is at-least-once; use the envelope's `eventId` to deduplicate\nretries. Event ordering is not guaranteed — use the envelope's\n`timestamp` when reconstructing a sequence. To fetch a message's current\nreaction set, call [`/reaction.list`](/docs/api/reaction-list).\n\n**Filter options:**\n- `names`: array of reaction names (e.g. `[\"wave\", \"heart\"]`) — only\n  receive events where the changed reaction is one of these names.\n\n**Note:** the legacy v0 `chat:message:reaction` event instead delivers the\nmessage's full reaction snapshot, debounced per message, with tagged IDs\n(`chat` / `recipient` / `messageSender`) — and its filter key is `codes`.\n\nBoth actors are typed inline. `userType` describes `userId`, while\n`messageAuthorType` describes `messageAuthorId`; each equals\n[`user.info.type`](/docs/api/user-info) under the same credentials.\n\nThese fields are additive on both supported webhook versions. Baseline\n(`2026-06-01`) includes them in the bare payload; Latest nests them under\n`data`:\n\n```json\n{ \"userId\": \"ad1e9cc0-...\", \"userType\": \"user\", \"messageAuthorId\": \"709b8a57-...\", \"messageAuthorType\": \"bot\" }\n{ \"type\": \"chat.reaction\", \"data\": { \"userId\": \"ad1e9cc0-...\", \"userType\": \"user\", \"messageAuthorId\": \"709b8a57-...\", \"messageAuthorType\": \"bot\" } }\n```\n\nA malformed event whose actor or message author cannot be resolved is not\ndelivered as a partially typed payload.\n",
        "operationId": "chat.reaction",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "chat.reaction"
                        ],
                        "description": "Discriminator — always `chat.reaction` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "action": {
                            "type": "string",
                            "enum": [
                              "added",
                              "removed"
                            ],
                            "description": "Whether the reaction was added or removed"
                          },
                          "chatId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The chat containing the message that was reacted to"
                          },
                          "chatType": {
                            "type": "string",
                            "description": "Type of chat the message is in (e.g. `dm`, `group`)."
                          },
                          "messageId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "ID of the message that was reacted to"
                          },
                          "messageTimestamp": {
                            "type": "integer",
                            "description": "Timestamp (Unix microseconds) of the reacted message — its stable key within the chat"
                          },
                          "threadTimestamp": {
                            "type": "integer",
                            "description": "Timestamp of the parent thread message (Unix microseconds); present only for reactions on a thread reply"
                          },
                          "userId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Principal ID of the actor whose reaction changed, matching the IDs in `/reaction.list`."
                          },
                          "userType": {
                            "type": "string",
                            "enum": [
                              "user",
                              "bot"
                            ],
                            "description": "Principal type of `userId`."
                          },
                          "messageAuthorId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Principal ID of the reacted message's author. Use it to filter to reactions on your app's own messages."
                          },
                          "messageAuthorType": {
                            "type": "string",
                            "enum": [
                              "user",
                              "bot"
                            ],
                            "description": "Principal type of `messageAuthorId`."
                          },
                          "name": {
                            "type": "string",
                            "description": "The reaction that changed (e.g. `wave`, `heart`), matching the `name` field of `/reaction.add` and `/reaction.list`."
                          },
                          "emojiText": {
                            "type": "string",
                            "description": "The rendered emoji (e.g. `👋`). May be absent on removals."
                          }
                        },
                        "required": [
                          "action",
                          "chatId",
                          "chatType",
                          "messageId",
                          "messageTimestamp",
                          "userId",
                          "userType",
                          "messageAuthorId",
                          "messageAuthorType",
                          "name"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "chat.reaction",
                "eventId": "0197f9a2-1a51-7aa1-8b2c-3d4e5f6a7b8c",
                "timestamp": "2025-06-02T23:21:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "action": "added",
                  "chatId": "8f3b9c2e-1a4d-4e7b-9c0a-2b6d1f5e3a7c",
                  "chatType": "group",
                  "messageId": "16933dd4-6e4c-4acf-9af1-d1972f9b4d34",
                  "messageTimestamp": 1748906400000000,
                  "userId": "ad1e9cc0-0ffd-47e5-895c-2630a73327b4",
                  "userType": "user",
                  "messageAuthorId": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                  "messageAuthorType": "bot",
                  "name": "wave",
                  "emojiText": "👋"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "lobby.booked": {
      "post": {
        "summary": "Lobby booking created",
        "description": "A new booking has been created for a lobby. Fires when a guest completes\nbooking for a lobby (via a lobby link/handle).\n\n**Event name:** `lobby.booked`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `lobby:read`\n\n**Note:** unlike the legacy v0 `lobby:booked` event, the `lobbyId` filter\nis not available on the v1 event — subscriptions receive all lobby\nbookings in the workspace.\n",
        "operationId": "lobby.booked",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "lobby.booked"
                        ],
                        "description": "Discriminator — always `lobby.booked` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "lobby": {
                            "$ref": "#/components/schemas/LobbyConfiguration"
                          },
                          "booking": {
                            "$ref": "#/components/schemas/LobbyBooking"
                          }
                        },
                        "required": [
                          "lobby",
                          "booking"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "lobby.booked",
                "eventId": "0197f9ac-b4fb-7aab-a5c0-3b4c5d6e7f80",
                "timestamp": "2026-07-07T18:23:45.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "lobby": {
                    "id": "5c8d2f1a-9b3e-4c7d-8a1f-2e6b4d9c0a3e",
                    "slug": "office-hours",
                    "displayName": "Office Hours",
                    "active": true,
                    "url": "https://ro.am/lobby/office-hours",
                    "handle": "office-hours"
                  },
                  "booking": {
                    "id": "c2a4e6f8-1b3d-5a7c-9e0f-4d6b8a2c1e3f",
                    "start": "2026-07-09T16:00:00Z",
                    "end": "2026-07-09T16:30:00Z",
                    "status": "confirmed",
                    "timeZone": "America/New_York",
                    "meetingLink": "https://ro.am/m/9k2x7q1w",
                    "notes": "Looking forward to discussing the integration.",
                    "created": "2026-07-07T18:23:45Z",
                    "hosts": [
                      {
                        "name": "Alex Chen",
                        "email": "alex.chen@example.com",
                        "isOrganizer": true
                      }
                    ],
                    "invitees": [
                      {
                        "name": "Jordan Smith",
                        "email": "jordan@example.com",
                        "status": "accepted",
                        "isBooker": true
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "magicast.created": {
      "post": {
        "summary": "Magicast created",
        "description": "A Magicast has been created.\n\n**Event name:** `magicast.created`\n\nThe payload is metadata only. It does **not** include transcript `cues` or\na signed `videoUrl` (those expire / can be large). Use the `id` with\n[`/magicast.info`](/docs/api/magicast-info) for cues, chapters, and video.\nIf `videoStatus` is `processing`, the player share link can still be minted\nwith [`/magicast.shareLink`](/docs/api/magicast-share-link).\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `magicast:read`\n",
        "operationId": "magicast.created",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "magicast.created"
                        ],
                        "description": "Discriminator — always `magicast.created` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/MagicastCreated"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "magicast.created",
                "eventId": "0197f9a3-2b62-7bb2-9c3d-4e5f6a7b8c9d",
                "timestamp": "2025-03-15T10:30:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
                  "name": "Weekly Standup Recording",
                  "createdAt": "2025-03-15T10:30:00Z",
                  "ownerId": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                  "coverImageUrl": "https://ro.am/magicast-covers/d4e5f6a7-b8c9-0123-4567-89abcdef0123",
                  "durationMs": 184000,
                  "videoStatus": "available",
                  "hasTranscript": true,
                  "chapters": [
                    {
                      "title": "Updates",
                      "startTime": 0
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "meeting.started": {
      "post": {
        "summary": "Meeting started",
        "description": "Fires when a meeting starts. Personal destinations only receive meetings\nthe authorizing user participated in. Organization destinations are\nroam-wide only with [`admin:meetings:read`](/docs/guides/scopes#meeting-width-adminmeetingsread);\notherwise the install's bot must have access to the meeting.\n\n**Event name:** `meeting.started`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `meetings:read` (`user:read` to include `host`,\n`user:read.email` for the host's email).\n",
        "operationId": "meeting.started",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "meeting.started"
                        ],
                        "description": "Discriminator — always `meeting.started` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Meeting ID."
                          },
                          "title": {
                            "type": "string",
                            "description": "Meeting title or location."
                          },
                          "start": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Meeting start time (RFC3339, UTC)."
                          },
                          "host": {
                            "description": "Meeting host. Present when the destination has `user:read`;\nthe email field requires `user:read.email`. Omitted if the\nhost cannot be resolved.\n",
                            "$ref": "#/components/schemas/MeetingParticipant"
                          }
                        },
                        "required": [
                          "id",
                          "start"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "meeting.started",
                "eventId": "0197f9a4-3c73-7cc3-ad4e-5f6a7b8c9d0e",
                "timestamp": "2026-04-21T17:00:02.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "title": "Weekly Team Standup",
                  "start": "2026-04-21T17:00:00Z",
                  "host": {
                    "type": "member",
                    "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                    "name": "Alex Chen",
                    "email": "alex.chen@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "meeting.ended": {
      "post": {
        "summary": "Meeting ended",
        "description": "Fires once a meeting's transcript and AI summary are ready. The\npayload mirrors [`/meeting.info`](/docs/api/meeting-info) so handlers\ndo not need a follow-up fetch in the common case.\n\nPersonal destinations only receive meetings the authorizing user\nparticipated in. Organization destinations are roam-wide only with\n[`admin:meetings:read`](/docs/guides/scopes#meeting-width-adminmeetingsread);\notherwise the install's bot must have access to the meeting.\n\n**Event name:** `meeting.ended`\n\n**Filter:** `{\"hasVideo\": true}` — only deliver when the meeting was\nvideo recorded. The filter keys on \"was recorded\", not \"the recording is\nready to fetch\", so a matching delivery normally arrives while the upload\nis still in flight. Omit the filter to receive every `meeting.ended`\nevent. `{\"hasVideo\": false}` is rejected at subscribe time so it is not\nsilently treated as no filter.\n\n**No `videoStatus` here.** [`/meeting.info`](/docs/api/meeting-info) and\n[`/meeting.list`](/docs/api/meeting-list) carry a `videoStatus` field\n(`none`, `processing`, `available`); this payload deliberately does not.\n`meeting.ended` fires once, at meeting end, when the value would be\n`processing` for essentially every recorded meeting and would already be\nstale by the time the delivery landed. To find out whether a recording is\nplayable, call [`/meeting.info`](/docs/api/meeting-info) for a fresh read.\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `meetings:read` (`user:read` to include\nparticipants and the host, `user:read.email` for emails).\n",
        "operationId": "meeting.ended",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "meeting.ended"
                        ],
                        "description": "Discriminator — always `meeting.ended` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Meeting ID."
                          },
                          "title": {
                            "type": "string"
                          },
                          "subtitle": {
                            "type": "string"
                          },
                          "start": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Meeting start time (RFC3339, UTC)."
                          },
                          "participantCount": {
                            "type": "integer",
                            "description": "Total number of participants."
                          },
                          "hasVideo": {
                            "type": "boolean",
                            "description": "Whether the meeting was video recorded — a video track exists.\n`true` at meeting end for every recorded meeting, and it never\nflips back. It does **not** mean the recording is ready to fetch\nor play: when this event fires the upload has almost never\nfinished. Call [`/meeting.info`](/docs/api/meeting-info) and read\n`videoStatus` for that. Same semantics as\n[`/meeting.info`](/docs/api/meeting-info),\n[`/meeting.list`](/docs/api/meeting-list), and the\n`{\"hasVideo\": true}` subscription filter.\n"
                          },
                          "host": {
                            "description": "Meeting host. Requires `user:read`; email requires\n`user:read.email`. Omitted if the host cannot be resolved.\n",
                            "$ref": "#/components/schemas/MeetingParticipant"
                          },
                          "participants": {
                            "type": "array",
                            "description": "Resolved participants. Requires `user:read`.",
                            "items": {
                              "$ref": "#/components/schemas/MeetingParticipant"
                            }
                          },
                          "summary": {
                            "type": "string",
                            "description": "AI-generated meeting summary."
                          },
                          "actionItems": {
                            "type": "array",
                            "description": "AI-extracted action items.",
                            "items": {
                              "$ref": "#/components/schemas/ActionItem"
                            }
                          },
                          "chapters": {
                            "type": "array",
                            "description": "Meeting chapters/segments.",
                            "items": {
                              "type": "object",
                              "properties": {
                                "name": {
                                  "type": "string"
                                },
                                "start": {
                                  "type": "integer",
                                  "description": "Offset in milliseconds since the meeting's `start`."
                                },
                                "synopsis": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        },
                        "required": [
                          "id",
                          "start",
                          "participantCount",
                          "hasVideo"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "meeting.ended",
                "eventId": "0197f9a5-4d84-7dd4-be5f-6a7b8c9d0e1f",
                "timestamp": "2026-04-21T17:52:18.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "title": "Weekly Team Standup",
                  "start": "2026-04-21T17:00:00Z",
                  "participantCount": 8,
                  "hasVideo": true,
                  "host": {
                    "type": "member",
                    "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                    "name": "Alex Chen",
                    "email": "alex.chen@example.com"
                  },
                  "summary": "The team reviewed Q2 priorities and assigned next-sprint action items.",
                  "actionItems": [
                    {
                      "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
                      "title": "Update API documentation",
                      "description": "Add v1 meeting endpoints to the developer docs",
                      "complete": false,
                      "assigneeId": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                      "suggestedAssigneeName": "Alex Chen"
                    }
                  ],
                  "chapters": [
                    {
                      "name": "Sprint Review",
                      "start": 0,
                      "synopsis": "Reviewed completed tasks from the previous sprint"
                    },
                    {
                      "name": "Q2 Planning",
                      "start": 1140000,
                      "synopsis": "Discussed priorities and resource allocation for Q2"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "user.status.update": {
      "post": {
        "summary": "User status update",
        "description": "A user's presence status has changed (checked in or checked out of the Roam).\n\n**Event name:** `user.status.update`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nThe event payload (`data`) is the user object with the updated `status`\nfield (`checkedIn` or `checkedOut`). `id`, `name`, and `status` are always\npresent; `email` requires the `user:read.email` scope.\n\n**Required scope:** `user:read.status` (subscribe and delivery both require this\nexact scope — `user:read` alone is not enough). Add `user:read.email` to include\nthe user's email address in the payload.\n",
        "operationId": "user.status.update",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "user.status.update"
                        ],
                        "description": "Discriminator — always `user.status.update` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/User"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "user.status.update",
                "eventId": "0197f9a6-5e95-7ee5-8f6a-7b8c9d0e1f2a",
                "timestamp": "2026-07-07T18:23:45.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                  "name": "Alex Chen",
                  "imageUrl": "https://ro.am/card-images/7be550c0-6994-4b8f-9a41-48825c6fc62a",
                  "email": "alex.chen@example.com",
                  "isAdmin": false,
                  "status": "checkedIn"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "onair.event.created": {
      "post": {
        "summary": "On-Air event created",
        "description": "A new On-Air event was created.\n\n**Event name:** `onair.event.created`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive deliveries for the specified On-Air event\n",
        "operationId": "onair.event.created",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.event.created"
                        ],
                        "description": "Discriminator — always `onair.event.created` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/OnAirWebhookEvent"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.event.created",
                "eventId": "0197f9a7-6fa6-7aa6-907b-8c9d0e1f2a3b",
                "timestamp": "2026-06-15T09:30:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                  "title": "Product Launch",
                  "slug": "product-launch",
                  "start": "2026-07-01T17:00:00Z",
                  "end": "2026-07-01T18:00:00Z",
                  "timeZone": "America/Los_Angeles",
                  "eventPageUrl": "https://ro.am/onair/product-launch",
                  "enableSEO": true,
                  "autoAdmit": false,
                  "disableRSVP": false,
                  "hosts": [
                    {
                      "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                      "name": "Alex Chen"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.event.updated": {
      "post": {
        "summary": "On-Air event updated",
        "description": "An On-Air event was updated.\n\n**Event name:** `onair.event.updated`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive deliveries for the specified On-Air event\n",
        "operationId": "onair.event.updated",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.event.updated"
                        ],
                        "description": "Discriminator — always `onair.event.updated` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/OnAirWebhookEvent"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.event.updated",
                "eventId": "0197f9a8-70b7-7bb7-a18c-9d0e1f2a3b4c",
                "timestamp": "2026-06-22T11:12:13.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                  "title": "Product Launch (rescheduled)",
                  "slug": "product-launch",
                  "start": "2026-07-02T17:00:00Z",
                  "end": "2026-07-02T18:00:00Z",
                  "timeZone": "America/Los_Angeles",
                  "eventPageUrl": "https://ro.am/onair/product-launch",
                  "enableSEO": true,
                  "autoAdmit": false,
                  "disableRSVP": false,
                  "hosts": [
                    {
                      "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                      "name": "Alex Chen"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.event.canceled": {
      "post": {
        "summary": "On-Air event canceled",
        "description": "An On-Air event was canceled. Only the event's `id` and `title` are sent.\n\n**Event name:** `onair.event.canceled`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive deliveries for the specified On-Air event\n",
        "operationId": "onair.event.canceled",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.event.canceled"
                        ],
                        "description": "Discriminator — always `onair.event.canceled` for this event."
                      },
                      "data": {
                        "type": "object",
                        "description": "The canceled event (identity only).",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The event's unique identifier."
                          },
                          "title": {
                            "type": "string",
                            "description": "Event title."
                          }
                        },
                        "required": [
                          "id",
                          "title"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.event.canceled",
                "eventId": "0197f9a9-81c8-7cc8-b29d-0e1f2a3b4c5d",
                "timestamp": "2026-06-25T16:45:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                  "title": "Product Launch"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.guest.rsvp": {
      "post": {
        "summary": "On-Air guest RSVP changed",
        "description": "A guest's RSVP status for an On-Air event changed.\n\n**Event name:** `onair.guest.rsvp`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive RSVPs for the specified event\n- `status`: one of `invited`, `going`, `maybe`, `notGoing` — only receive RSVPs that changed to this status\n",
        "operationId": "onair.guest.rsvp",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.guest.rsvp"
                        ],
                        "description": "Discriminator — always `onair.guest.rsvp` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "event": {
                            "type": "object",
                            "description": "The event the guest belongs to (identity only).",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The event's unique identifier."
                              },
                              "title": {
                                "type": "string",
                                "description": "Event title."
                              }
                            },
                            "required": [
                              "id",
                              "title"
                            ]
                          },
                          "guest": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The guest's unique identifier."
                              },
                              "email": {
                                "type": "string",
                                "format": "email",
                                "description": "Guest email. Omitted when unset."
                              },
                              "name": {
                                "type": "string",
                                "description": "Guest name. Omitted when unset."
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "invited",
                                  "going",
                                  "maybe",
                                  "notGoing"
                                ],
                                "description": "The guest's new RSVP status."
                              },
                              "previousStatus": {
                                "type": "string",
                                "enum": [
                                  "invited",
                                  "going",
                                  "maybe",
                                  "notGoing"
                                ],
                                "description": "The guest's prior RSVP status. Omitted when not known."
                              }
                            },
                            "required": [
                              "id",
                              "status"
                            ]
                          }
                        },
                        "required": [
                          "event",
                          "guest"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.guest.rsvp",
                "eventId": "0197f9aa-92d9-7dd9-83ae-1f2a3b4c5d6e",
                "timestamp": "2026-06-18T14:03:27.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "event": {
                    "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                    "title": "Product Launch"
                  },
                  "guest": {
                    "id": "af6663d5-0f37-4105-95df-4fea20ef7c7c",
                    "email": "jordan@example.com",
                    "name": "Jordan Smith",
                    "status": "going",
                    "previousStatus": "invited"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.guest.added": {
      "post": {
        "summary": "On-Air guests added",
        "description": "One or more guests were added to an On-Air event.\n\n**Event name:** `onair.guest.added`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive additions for the specified event\n",
        "operationId": "onair.guest.added",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.guest.added"
                        ],
                        "description": "Discriminator — always `onair.guest.added` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "event": {
                            "type": "object",
                            "description": "The event the guests were added to (identity only).",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The event's unique identifier."
                              },
                              "title": {
                                "type": "string",
                                "description": "Event title."
                              }
                            },
                            "required": [
                              "id",
                              "title"
                            ]
                          },
                          "guests": {
                            "type": "array",
                            "description": "The guests that were added (non-empty).",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "description": "The guest's unique identifier."
                                },
                                "email": {
                                  "type": "string",
                                  "format": "email",
                                  "description": "Guest email. Omitted when unset."
                                },
                                "name": {
                                  "type": "string",
                                  "description": "Guest name. Omitted when unset."
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "invited",
                                    "going",
                                    "maybe",
                                    "notGoing"
                                  ],
                                  "description": "The guest's RSVP status."
                                }
                              },
                              "required": [
                                "id",
                                "status"
                              ]
                            }
                          }
                        },
                        "required": [
                          "event",
                          "guests"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.guest.added",
                "eventId": "0197f9ab-a3ea-7eea-94bf-2a3b4c5d6f70",
                "timestamp": "2026-06-16T10:15:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "event": {
                    "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                    "title": "Product Launch"
                  },
                  "guests": [
                    {
                      "id": "af6663d5-0f37-4105-95df-4fea20ef7c7c",
                      "email": "jordan@example.com",
                      "name": "Jordan Smith",
                      "status": "invited"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "token.revoked": {
      "post": {
        "summary": "Token revoked",
        "description": "Fires when an OAuth access token belonging to your app is revoked by any\npath: the user, an admin, your own `token.revoke` call, or SCIM user\ndeactivation.\n\n**Event name:** `token.revoked`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nDelivery is authenticated by your webhook signing secret — not by the\nrevoked token — so you still receive this event after the grant is gone.\nYou may also receive it for a token you no longer hold (for example after\ncalling `token.revoke` yourself); treat handling as idempotent.\n\nThis event does **not** mean the app was uninstalled. Webhook subscriptions\nand other users' tokens are untouched unless a separate uninstall path\nalso runs. See [`app.uninstalled`](/docs/webhooks/app-uninstalled).\n\n**Required scope:** `webhook:write` (the same scope needed to subscribe).\n",
        "operationId": "token.revoked",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "token.revoked"
                        ],
                        "description": "Discriminator — always `token.revoked` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "clientId": {
                            "type": "string",
                            "description": "Your OAuth app's client ID."
                          },
                          "personId": {
                            "type": "integer",
                            "format": "int64",
                            "description": "Present for personal-mode tokens — the authorizing\nperson's ID. Omitted for organization-mode tokens.\n"
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "user_revoked",
                              "admin_revoked",
                              "api_revoked",
                              "user_deactivated"
                            ],
                            "description": "Why the token was revoked:\n- `user_revoked` — end user revoked the connected app\n- `admin_revoked` — admin revoked a connection or rejected approval\n- `api_revoked` — your app called `token.revoke` (or v0 `app.uninstall`)\n- `user_deactivated` — the authorizing user was deactivated (e.g. SCIM)\n"
                          }
                        },
                        "required": [
                          "clientId",
                          "reason"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "token.revoked",
                "eventId": "0197f9b0-1a2b-7cc3-9d0e-1f2a3b4c5d6e",
                "timestamp": "2026-07-20T18:00:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "clientId": "9f3c2a1b0e8d7c6b5a49382716150493",
                  "personId": 12345,
                  "reason": "user_revoked"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "app.uninstalled": {
      "post": {
        "summary": "App uninstalled",
        "description": "Fires when your app's installation is removed from a Roam — for example an\nadmin revokes the installed app, or the developer archives the API client.\nRoam enqueues this delivery **before** tearing down your webhook\nsubscriptions and tokens, so you get a last-gasp signed callback.\n\n**Event name:** `app.uninstalled`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nDelivery is authenticated by your webhook signing secret. After this event,\ndiscard all tokens for the install and stop retrying API calls.\n\nCalling [`token.revoke`](/docs/api/token-revoke) does **not** fire\n`app.uninstalled` — that endpoint only kills the presented grant and fires\n[`token.revoked`](/docs/webhooks/token-revoked).\n\n**Required scope:** `webhook:write` (the same scope needed to subscribe).\n",
        "operationId": "app.uninstalled",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "app.uninstalled"
                        ],
                        "description": "Discriminator — always `app.uninstalled` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "clientId": {
                            "type": "string",
                            "description": "Your OAuth app's client ID."
                          },
                          "roamId": {
                            "type": "string",
                            "description": "Public external ID of the Roam the install was removed\nfrom, when known. Omitted when the uninstall is not\nroam-scoped (e.g. archiving the client globally).\n"
                          }
                        },
                        "required": [
                          "clientId"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "app.uninstalled",
                "eventId": "0197f9b1-2b3c-7dd4-ae1f-2a3b4c5d6e7f",
                "timestamp": "2026-07-20T18:05:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "clientId": "9f3c2a1b0e8d7c6b5a49382716150493",
                  "roamId": "12QJUKKE0X"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    }
  },
  "x-webhooks": {
    "chat.link.shared": {
      "post": {
        "summary": "Matching link shared",
        "description": "A newly created text or markdown message contains one or more links that\nmatch this app's registered unfurl domains.\n\n**Event name:** `chat.link.shared`\n\n**Required scope:** `links:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope).\nURLs are unique, ordered by first appearance, and filtered separately for\neach installation. Edits and messages sent by the same app installation do\nnot produce this event.\n\nThe app receives only messages in chats it can see: organization apps must\nbe chat members or the chat must be a public group; personal installations\nrequire their owner to be in the chat.\n",
        "operationId": "chat.link.shared",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "chat.link.shared"
                        ],
                        "description": "Discriminator — always `chat.link.shared` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/LinkSharedEventData"
                      }
                    },
                    "required": [
                      "data"
                    ]
                  }
                ]
              },
              "example": {
                "type": "chat.link.shared",
                "eventId": "0197f9a2-1a51-7aa1-8b2c-3d4e5f6a7b8c",
                "timestamp": "2026-07-20T15:30:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "chatId": "8f3b9c2e-1a4d-4e7b-9c0a-2b6d1f5e3a7c",
                  "chatType": "group",
                  "messageId": "16933dd4-6e4c-4acf-9af1-d1972f9b4d34",
                  "messageTimestamp": 1748906400000000,
                  "userId": "ad1e9cc0-0ffd-47e5-895c-2630a73327b4",
                  "links": [
                    {
                      "url": "https://status.example.com/incidents/123",
                      "domain": "status.example.com"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return 200 to acknowledge the delivery."
          }
        }
      }
    },
    "chat.message": {
      "post": {
        "summary": "Chat message received",
        "description": "A chat message addressed to the app — fired when a message is **created,\nedited, or deleted**.\n\n**Event name:** `chat.message`\n\n**Required scope:** `chat:history`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nDelivery follows **membership**:\n\n- **Organization:** every message in chats the app is a member of (groups\n  and DMs). @-mentioning the app does not add it to a group.\n- **Personal:** every message in chats the **owner** is a member of\n  (groups and DMs with other people), plus the `{bot, owner}` self-DM.\n  Personal bots cannot be added as group members.\n\nSubscribe with `{ \"mention\": true }` to receive only messages that\n@-mention the app.\n\n## Message lifecycle: create, edit, delete\n\nThe same `chat.message` event fires for all three operations — the\n`Roam-Event-Type` header is always `chat.message`. Dispatch on the event,\nthen read the `data.version` field (the message's edit revision) to tell\nthem apart:\n\n| Operation | How to recognize it | Payload |\n|-----------|---------------------|---------|\n| New message | `data.version` is `1` | Full content |\n| Edit | `data.version` > `1`, content present | The **new** content |\n| Delete | `data.contentType` is `\"deleted\"` (version bumped again) | Identity only, no content |\n\nA message keeps a stable identity of **`(chatId, timestamp)`** across its\nlifecycle, and `version` increases on every change (so it also orders\nevents). To mirror chat, upsert/remove by that key: a later event for the\nsame key with a **higher `version`** supersedes your local copy, and a\n`\"deleted\"` event removes it. The prior content of an edited or deleted\nmessage is not re-sent — you already received it on the original event.\n\nEdits and deletes are **v1-only**: the legacy v0 `chat:message:*` events\nremain create-only. A delete carries no mentions, so a `{ \"mention\": true }`\nfilter never matches a delete (key on the original message you received).\n\n**Adding an organization app to a group:**\n1. Open the group in Roam\n2. Click **Add Members**\n3. Search for your app by name\n4. Add the app to the group\n\nOr call [`group.join`](/docs/api/group-join) (self-join a public group) or\n[`group.add`](/docs/api/group-add). @-mention does not join. In a public\ngroup the Roam client may prompt **Invite {app} to this group?** when\nsomeone @-mentions a non-member org app; dismissing still sends the\nmessage and the app is not added.\n\nOnce added, the app receives every message in that group (unless you\nfilter). Thread replies use the same membership rule as top-level\nmessages.\n\n**Filter options:**\n- `chatType`: `\"dm\"` or `\"group\"` — Filter by chat type\n- `mention`: `true` — Only messages that @-mention the app\n\n`data.userType` is the principal type of `data.userId` (`user` or `bot`).\nIt always matches [`user.info`](/docs/api/user-info) under the same app\ncredentials, so check it to prevent bot-to-bot loops.\n\nMessages posted with a per-message sender display override carry it in the\noptional `data.sender` object (`name` / `imageUrl`); `data.userId` remains\nthe authoring identity. See the\n[Sender Profiles guide](/docs/guides/sender-profiles).\n\nPoll messages arrive with `contentType: \"poll\"` and a `poll` object\n(`question`, `options`, `allowMultipleAnswers`, `closesAt`) — the same\nshape [`/chat.history`](/docs/api/chat-history) returns. Vote tallies are\nnot on this event; call [`/reaction.list`](/docs/api/reaction-list)\n(`pollVotes`).\n\nThe field is additive on both supported webhook versions. Baseline\n(`2026-06-01`) delivers it in the bare payload; Latest nests the same fields\nunder `data`:\n\n```json\n{ \"userId\": \"b7c34e90-...\", \"userType\": \"bot\", \"contentType\": \"text\" }\n{ \"type\": \"chat.message\", \"data\": { \"userId\": \"b7c34e90-...\", \"userType\": \"bot\", \"contentType\": \"text\" } }\n```\n\nA malformed event whose author cannot be resolved as a visible principal is\nnot delivered with a missing type field.\n",
        "operationId": "chat.message",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "chat.message"
                        ],
                        "description": "Discriminator — always `chat.message` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "version": {
                            "type": "integer",
                            "description": "The message's edit revision: `1` for a newly-created message,\n`2`+ for an edit (the revision count), and bumped again on a\ndelete. Use it to order events for a `(chatId, timestamp)` key\nand to distinguish create / edit / delete (see the lifecycle\ntable above).\n"
                          },
                          "contentType": {
                            "type": "string",
                            "description": "Content type of the message: `text`, `voice`, `block`, or\n`poll`. A delete tombstone has `contentType: \"deleted\"`\nand carries no content fields.\n"
                          },
                          "userId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Principal ID of the message sender"
                          },
                          "userType": {
                            "type": "string",
                            "enum": [
                              "user",
                              "bot"
                            ],
                            "description": "Principal type of `userId`; equals `user.info.type` under the same credentials."
                          },
                          "sender": {
                            "type": "object",
                            "description": "Per-message sender display override supplied at send time.\nPresent only when the message carries one; `userId` remains\nthe authoring identity. See the\n[Sender Profiles guide](/docs/guides/sender-profiles).\n",
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "Display name override for this message."
                              },
                              "imageUrl": {
                                "type": "string",
                                "format": "uri",
                                "description": "Avatar URL override for this message."
                              }
                            }
                          },
                          "chatId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "ID of the chat where the message was sent"
                          },
                          "chatType": {
                            "type": "string",
                            "enum": [
                              "dm",
                              "group"
                            ],
                            "description": "Type of chat — `dm` (direct message) or `group` (group chat)"
                          },
                          "timestamp": {
                            "type": "integer",
                            "description": "Message key as Unix microseconds. Stable across the message's lifecycle."
                          },
                          "threadTimestamp": {
                            "type": "integer",
                            "description": "Unix microseconds timestamp of the parent message (thread replies only)"
                          },
                          "replyTimestamp": {
                            "type": "integer",
                            "description": "Timestamp of the message this one quotes (DM or channel-thread quoted replies)"
                          },
                          "text": {
                            "type": "string",
                            "description": "Text of the message, formatted as GitHub-flavored markdown. Absent on deletes and non-text messages. Mention tokens use Slack's syntax — `<@ID>` for principals (users and bots), `<!subteam^ID>` for groups and channels, `<!channel>` for the broadcast keyword."
                          },
                          "items": {
                            "type": "array",
                            "description": "File attachments (images, documents, etc.)",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "description": "UUID identifying this item"
                                },
                                "created": {
                                  "type": "string",
                                  "format": "date-time",
                                  "description": "When the item was created"
                                },
                                "type": {
                                  "type": "string",
                                  "enum": [
                                    "photo",
                                    "blob"
                                  ],
                                  "description": "Type of item: photo (images) or blob (other files)"
                                },
                                "mime": {
                                  "type": "string",
                                  "description": "MIME type of the file"
                                },
                                "name": {
                                  "type": "string",
                                  "description": "Filename"
                                },
                                "url": {
                                  "type": "string",
                                  "format": "uri",
                                  "description": "Download URL for the file"
                                },
                                "thumbnail": {
                                  "type": "string",
                                  "format": "uri",
                                  "description": "Thumbnail URL (images only)"
                                },
                                "size": {
                                  "type": "integer",
                                  "description": "File size in bytes"
                                }
                              },
                              "required": [
                                "id",
                                "type",
                                "name",
                                "url"
                              ]
                            }
                          },
                          "voice": {
                            "type": "object",
                            "description": "Voice message content, present when `contentType` is `voice`.",
                            "properties": {
                              "audioUrl": {
                                "type": "string",
                                "format": "uri",
                                "description": "URL of the voice audio."
                              },
                              "duration": {
                                "type": "integer",
                                "description": "Duration of the voice message in seconds."
                              },
                              "transcript": {
                                "type": "string",
                                "description": "Transcript of the voice message, if available."
                              }
                            }
                          },
                          "poll": {
                            "type": "object",
                            "description": "Poll content, present when `contentType` is `poll`.",
                            "properties": {
                              "question": {
                                "type": "string",
                                "description": "The poll question."
                              },
                              "options": {
                                "type": "array",
                                "description": "The poll answer options.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "id": {
                                      "type": "string",
                                      "description": "Unique option identifier."
                                    },
                                    "text": {
                                      "type": "string",
                                      "description": "Option display text."
                                    }
                                  }
                                }
                              },
                              "allowMultipleAnswers": {
                                "type": "boolean",
                                "description": "Whether voters can select multiple options."
                              },
                              "closesAt": {
                                "type": "string",
                                "format": "date-time",
                                "description": "When the poll closes (RFC3339 UTC). Omitted if no close time is set."
                              }
                            }
                          },
                          "blocks": {
                            "type": "array",
                            "description": "Block Kit blocks, present on block messages.",
                            "items": {
                              "type": "object"
                            }
                          },
                          "color": {
                            "type": "string",
                            "description": "Color strip for block messages (`good`, `warning`, `danger`, or a hex value)."
                          }
                        },
                        "required": [
                          "version",
                          "contentType",
                          "userId",
                          "userType",
                          "chatId",
                          "chatType",
                          "timestamp"
                        ]
                      }
                    }
                  }
                ]
              },
              "examples": {
                "new": {
                  "summary": "New message (version 1)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-7d2e-7cc3-9f6a-8b1c2d3e4f5a",
                    "timestamp": "2024-06-20T16:13:20.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 1,
                      "contentType": "text",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900000000000,
                      "text": "Deploying v2.3.0 now"
                    }
                  }
                },
                "poll": {
                  "summary": "Poll message (contentType poll)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-7d2e-7cc3-9f6a-8b1c2d3e4f5b",
                    "timestamp": "2024-06-20T16:15:00.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 1,
                      "contentType": "poll",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900100000000,
                      "poll": {
                        "question": "Favorite letter?",
                        "allowMultipleAnswers": true,
                        "closesAt": "2024-06-21T16:15:00Z",
                        "options": [
                          {
                            "id": "opt-a",
                            "text": "A"
                          },
                          {
                            "id": "opt-b",
                            "text": "B"
                          }
                        ]
                      }
                    }
                  }
                },
                "edit": {
                  "summary": "Edit (version 2, carries the new content)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-8e3f-7dd4-a07b-9c2d3e4f5a6b",
                    "timestamp": "2024-06-20T16:20:41.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 2,
                      "contentType": "text",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900000000000,
                      "text": "Deploying v2.3.1 now (hotfix included)"
                    }
                  }
                },
                "delete": {
                  "summary": "Delete (contentType deleted, identity only)",
                  "value": {
                    "type": "chat.message",
                    "eventId": "0197f9a1-9f40-7ee5-b18c-ad3e4f5a6b7c",
                    "timestamp": "2024-06-20T16:25:03.000000Z",
                    "apiVersion": "2026-07-07",
                    "data": {
                      "version": 3,
                      "contentType": "deleted",
                      "userId": "b7c34e90-2f1a-4d8e-9a6b-1c2d3e4f5a6b",
                      "userType": "user",
                      "chatId": "3f9a1b2c-4d5e-6f70-8a9b-0c1d2e3f4a5b",
                      "chatType": "group",
                      "timestamp": 1718900000000000
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "chat.reaction": {
      "post": {
        "summary": "Reaction added or removed",
        "description": "A reaction was added to or removed from a chat message the app can see.\n\n**Event name:** `chat.reaction`\n\n**Required scope:** `chat:history`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nLike `chat.message`, this is delivered for any message in a chat the app\nparticipates in or can see (Slack `reaction_added` / `reaction_removed`\nparity) — not only the app's own messages. The reacted message's author is\nin `messageAuthorId`, so an app can filter to reactions on its own\nmessages.\n\n**One event is delivered per reaction change**: `action` says whether the\nreaction was added or removed, `name` is the single reaction that changed,\nand `userId` is the user who changed it. There is no batching or\ndebouncing — an add followed by a remove delivers two events.\n\nDelivery is at-least-once; use the envelope's `eventId` to deduplicate\nretries. Event ordering is not guaranteed — use the envelope's\n`timestamp` when reconstructing a sequence. To fetch a message's current\nreaction set, call [`/reaction.list`](/docs/api/reaction-list).\n\n**Filter options:**\n- `names`: array of reaction names (e.g. `[\"wave\", \"heart\"]`) — only\n  receive events where the changed reaction is one of these names.\n\n**Note:** the legacy v0 `chat:message:reaction` event instead delivers the\nmessage's full reaction snapshot, debounced per message, with tagged IDs\n(`chat` / `recipient` / `messageSender`) — and its filter key is `codes`.\n\nBoth actors are typed inline. `userType` describes `userId`, while\n`messageAuthorType` describes `messageAuthorId`; each equals\n[`user.info.type`](/docs/api/user-info) under the same credentials.\n\nThese fields are additive on both supported webhook versions. Baseline\n(`2026-06-01`) includes them in the bare payload; Latest nests them under\n`data`:\n\n```json\n{ \"userId\": \"ad1e9cc0-...\", \"userType\": \"user\", \"messageAuthorId\": \"709b8a57-...\", \"messageAuthorType\": \"bot\" }\n{ \"type\": \"chat.reaction\", \"data\": { \"userId\": \"ad1e9cc0-...\", \"userType\": \"user\", \"messageAuthorId\": \"709b8a57-...\", \"messageAuthorType\": \"bot\" } }\n```\n\nA malformed event whose actor or message author cannot be resolved is not\ndelivered as a partially typed payload.\n",
        "operationId": "chat.reaction",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "chat.reaction"
                        ],
                        "description": "Discriminator — always `chat.reaction` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "action": {
                            "type": "string",
                            "enum": [
                              "added",
                              "removed"
                            ],
                            "description": "Whether the reaction was added or removed"
                          },
                          "chatId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The chat containing the message that was reacted to"
                          },
                          "chatType": {
                            "type": "string",
                            "description": "Type of chat the message is in (e.g. `dm`, `group`)."
                          },
                          "messageId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "ID of the message that was reacted to"
                          },
                          "messageTimestamp": {
                            "type": "integer",
                            "description": "Timestamp (Unix microseconds) of the reacted message — its stable key within the chat"
                          },
                          "threadTimestamp": {
                            "type": "integer",
                            "description": "Timestamp of the parent thread message (Unix microseconds); present only for reactions on a thread reply"
                          },
                          "userId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Principal ID of the actor whose reaction changed, matching the IDs in `/reaction.list`."
                          },
                          "userType": {
                            "type": "string",
                            "enum": [
                              "user",
                              "bot"
                            ],
                            "description": "Principal type of `userId`."
                          },
                          "messageAuthorId": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Principal ID of the reacted message's author. Use it to filter to reactions on your app's own messages."
                          },
                          "messageAuthorType": {
                            "type": "string",
                            "enum": [
                              "user",
                              "bot"
                            ],
                            "description": "Principal type of `messageAuthorId`."
                          },
                          "name": {
                            "type": "string",
                            "description": "The reaction that changed (e.g. `wave`, `heart`), matching the `name` field of `/reaction.add` and `/reaction.list`."
                          },
                          "emojiText": {
                            "type": "string",
                            "description": "The rendered emoji (e.g. `👋`). May be absent on removals."
                          }
                        },
                        "required": [
                          "action",
                          "chatId",
                          "chatType",
                          "messageId",
                          "messageTimestamp",
                          "userId",
                          "userType",
                          "messageAuthorId",
                          "messageAuthorType",
                          "name"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "chat.reaction",
                "eventId": "0197f9a2-1a51-7aa1-8b2c-3d4e5f6a7b8c",
                "timestamp": "2025-06-02T23:21:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "action": "added",
                  "chatId": "8f3b9c2e-1a4d-4e7b-9c0a-2b6d1f5e3a7c",
                  "chatType": "group",
                  "messageId": "16933dd4-6e4c-4acf-9af1-d1972f9b4d34",
                  "messageTimestamp": 1748906400000000,
                  "userId": "ad1e9cc0-0ffd-47e5-895c-2630a73327b4",
                  "userType": "user",
                  "messageAuthorId": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                  "messageAuthorType": "bot",
                  "name": "wave",
                  "emojiText": "👋"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "lobby.booked": {
      "post": {
        "summary": "Lobby booking created",
        "description": "A new booking has been created for a lobby. Fires when a guest completes\nbooking for a lobby (via a lobby link/handle).\n\n**Event name:** `lobby.booked`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `lobby:read`\n\n**Note:** unlike the legacy v0 `lobby:booked` event, the `lobbyId` filter\nis not available on the v1 event — subscriptions receive all lobby\nbookings in the workspace.\n",
        "operationId": "lobby.booked",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "lobby.booked"
                        ],
                        "description": "Discriminator — always `lobby.booked` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "lobby": {
                            "$ref": "#/components/schemas/LobbyConfiguration"
                          },
                          "booking": {
                            "$ref": "#/components/schemas/LobbyBooking"
                          }
                        },
                        "required": [
                          "lobby",
                          "booking"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "lobby.booked",
                "eventId": "0197f9ac-b4fb-7aab-a5c0-3b4c5d6e7f80",
                "timestamp": "2026-07-07T18:23:45.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "lobby": {
                    "id": "5c8d2f1a-9b3e-4c7d-8a1f-2e6b4d9c0a3e",
                    "slug": "office-hours",
                    "displayName": "Office Hours",
                    "active": true,
                    "url": "https://ro.am/lobby/office-hours",
                    "handle": "office-hours"
                  },
                  "booking": {
                    "id": "c2a4e6f8-1b3d-5a7c-9e0f-4d6b8a2c1e3f",
                    "start": "2026-07-09T16:00:00Z",
                    "end": "2026-07-09T16:30:00Z",
                    "status": "confirmed",
                    "timeZone": "America/New_York",
                    "meetingLink": "https://ro.am/m/9k2x7q1w",
                    "notes": "Looking forward to discussing the integration.",
                    "created": "2026-07-07T18:23:45Z",
                    "hosts": [
                      {
                        "name": "Alex Chen",
                        "email": "alex.chen@example.com",
                        "isOrganizer": true
                      }
                    ],
                    "invitees": [
                      {
                        "name": "Jordan Smith",
                        "email": "jordan@example.com",
                        "status": "accepted",
                        "isBooker": true
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "magicast.created": {
      "post": {
        "summary": "Magicast created",
        "description": "A Magicast has been created.\n\n**Event name:** `magicast.created`\n\nThe payload is metadata only. It does **not** include transcript `cues` or\na signed `videoUrl` (those expire / can be large). Use the `id` with\n[`/magicast.info`](/docs/api/magicast-info) for cues, chapters, and video.\nIf `videoStatus` is `processing`, the player share link can still be minted\nwith [`/magicast.shareLink`](/docs/api/magicast-share-link).\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `magicast:read`\n",
        "operationId": "magicast.created",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "magicast.created"
                        ],
                        "description": "Discriminator — always `magicast.created` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/MagicastCreated"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "magicast.created",
                "eventId": "0197f9a3-2b62-7bb2-9c3d-4e5f6a7b8c9d",
                "timestamp": "2025-03-15T10:30:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
                  "name": "Weekly Standup Recording",
                  "createdAt": "2025-03-15T10:30:00Z",
                  "ownerId": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                  "coverImageUrl": "https://ro.am/magicast-covers/d4e5f6a7-b8c9-0123-4567-89abcdef0123",
                  "durationMs": 184000,
                  "videoStatus": "available",
                  "hasTranscript": true,
                  "chapters": [
                    {
                      "title": "Updates",
                      "startTime": 0
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "meeting.started": {
      "post": {
        "summary": "Meeting started",
        "description": "Fires when a meeting starts. Personal destinations only receive meetings\nthe authorizing user participated in. Organization destinations are\nroam-wide only with [`admin:meetings:read`](/docs/guides/scopes#meeting-width-adminmeetingsread);\notherwise the install's bot must have access to the meeting.\n\n**Event name:** `meeting.started`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `meetings:read` (`user:read` to include `host`,\n`user:read.email` for the host's email).\n",
        "operationId": "meeting.started",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "meeting.started"
                        ],
                        "description": "Discriminator — always `meeting.started` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Meeting ID."
                          },
                          "title": {
                            "type": "string",
                            "description": "Meeting title or location."
                          },
                          "start": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Meeting start time (RFC3339, UTC)."
                          },
                          "host": {
                            "description": "Meeting host. Present when the destination has `user:read`;\nthe email field requires `user:read.email`. Omitted if the\nhost cannot be resolved.\n",
                            "$ref": "#/components/schemas/MeetingParticipant"
                          }
                        },
                        "required": [
                          "id",
                          "start"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "meeting.started",
                "eventId": "0197f9a4-3c73-7cc3-ad4e-5f6a7b8c9d0e",
                "timestamp": "2026-04-21T17:00:02.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "title": "Weekly Team Standup",
                  "start": "2026-04-21T17:00:00Z",
                  "host": {
                    "type": "member",
                    "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                    "name": "Alex Chen",
                    "email": "alex.chen@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "meeting.ended": {
      "post": {
        "summary": "Meeting ended",
        "description": "Fires once a meeting's transcript and AI summary are ready. The\npayload mirrors [`/meeting.info`](/docs/api/meeting-info) so handlers\ndo not need a follow-up fetch in the common case.\n\nPersonal destinations only receive meetings the authorizing user\nparticipated in. Organization destinations are roam-wide only with\n[`admin:meetings:read`](/docs/guides/scopes#meeting-width-adminmeetingsread);\notherwise the install's bot must have access to the meeting.\n\n**Event name:** `meeting.ended`\n\n**Filter:** `{\"hasVideo\": true}` — only deliver when the meeting was\nvideo recorded. The filter keys on \"was recorded\", not \"the recording is\nready to fetch\", so a matching delivery normally arrives while the upload\nis still in flight. Omit the filter to receive every `meeting.ended`\nevent. `{\"hasVideo\": false}` is rejected at subscribe time so it is not\nsilently treated as no filter.\n\n**No `videoStatus` here.** [`/meeting.info`](/docs/api/meeting-info) and\n[`/meeting.list`](/docs/api/meeting-list) carry a `videoStatus` field\n(`none`, `processing`, `available`); this payload deliberately does not.\n`meeting.ended` fires once, at meeting end, when the value would be\n`processing` for essentially every recorded meeting and would already be\nstale by the time the delivery landed. To find out whether a recording is\nplayable, call [`/meeting.info`](/docs/api/meeting-info) for a fresh read.\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Required scope:** `meetings:read` (`user:read` to include\nparticipants and the host, `user:read.email` for emails).\n",
        "operationId": "meeting.ended",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "meeting.ended"
                        ],
                        "description": "Discriminator — always `meeting.ended` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Meeting ID."
                          },
                          "title": {
                            "type": "string"
                          },
                          "subtitle": {
                            "type": "string"
                          },
                          "start": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Meeting start time (RFC3339, UTC)."
                          },
                          "participantCount": {
                            "type": "integer",
                            "description": "Total number of participants."
                          },
                          "hasVideo": {
                            "type": "boolean",
                            "description": "Whether the meeting was video recorded — a video track exists.\n`true` at meeting end for every recorded meeting, and it never\nflips back. It does **not** mean the recording is ready to fetch\nor play: when this event fires the upload has almost never\nfinished. Call [`/meeting.info`](/docs/api/meeting-info) and read\n`videoStatus` for that. Same semantics as\n[`/meeting.info`](/docs/api/meeting-info),\n[`/meeting.list`](/docs/api/meeting-list), and the\n`{\"hasVideo\": true}` subscription filter.\n"
                          },
                          "host": {
                            "description": "Meeting host. Requires `user:read`; email requires\n`user:read.email`. Omitted if the host cannot be resolved.\n",
                            "$ref": "#/components/schemas/MeetingParticipant"
                          },
                          "participants": {
                            "type": "array",
                            "description": "Resolved participants. Requires `user:read`.",
                            "items": {
                              "$ref": "#/components/schemas/MeetingParticipant"
                            }
                          },
                          "summary": {
                            "type": "string",
                            "description": "AI-generated meeting summary."
                          },
                          "actionItems": {
                            "type": "array",
                            "description": "AI-extracted action items.",
                            "items": {
                              "$ref": "#/components/schemas/ActionItem"
                            }
                          },
                          "chapters": {
                            "type": "array",
                            "description": "Meeting chapters/segments.",
                            "items": {
                              "type": "object",
                              "properties": {
                                "name": {
                                  "type": "string"
                                },
                                "start": {
                                  "type": "integer",
                                  "description": "Offset in milliseconds since the meeting's `start`."
                                },
                                "synopsis": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        },
                        "required": [
                          "id",
                          "start",
                          "participantCount",
                          "hasVideo"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "meeting.ended",
                "eventId": "0197f9a5-4d84-7dd4-be5f-6a7b8c9d0e1f",
                "timestamp": "2026-04-21T17:52:18.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "title": "Weekly Team Standup",
                  "start": "2026-04-21T17:00:00Z",
                  "participantCount": 8,
                  "hasVideo": true,
                  "host": {
                    "type": "member",
                    "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                    "name": "Alex Chen",
                    "email": "alex.chen@example.com"
                  },
                  "summary": "The team reviewed Q2 priorities and assigned next-sprint action items.",
                  "actionItems": [
                    {
                      "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
                      "title": "Update API documentation",
                      "description": "Add v1 meeting endpoints to the developer docs",
                      "complete": false,
                      "assigneeId": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                      "suggestedAssigneeName": "Alex Chen"
                    }
                  ],
                  "chapters": [
                    {
                      "name": "Sprint Review",
                      "start": 0,
                      "synopsis": "Reviewed completed tasks from the previous sprint"
                    },
                    {
                      "name": "Q2 Planning",
                      "start": 1140000,
                      "synopsis": "Discussed priorities and resource allocation for Q2"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "user.status.update": {
      "post": {
        "summary": "User status update",
        "description": "A user's presence status has changed (checked in or checked out of the Roam).\n\n**Event name:** `user.status.update`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nThe event payload (`data`) is the user object with the updated `status`\nfield (`checkedIn` or `checkedOut`). `id`, `name`, and `status` are always\npresent; `email` requires the `user:read.email` scope.\n\n**Required scope:** `user:read.status` (subscribe and delivery both require this\nexact scope — `user:read` alone is not enough). Add `user:read.email` to include\nthe user's email address in the payload.\n",
        "operationId": "user.status.update",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "user.status.update"
                        ],
                        "description": "Discriminator — always `user.status.update` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/User"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "user.status.update",
                "eventId": "0197f9a6-5e95-7ee5-8f6a-7b8c9d0e1f2a",
                "timestamp": "2026-07-07T18:23:45.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                  "name": "Alex Chen",
                  "imageUrl": "https://ro.am/card-images/7be550c0-6994-4b8f-9a41-48825c6fc62a",
                  "email": "alex.chen@example.com",
                  "isAdmin": false,
                  "status": "checkedIn"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "onair.event.created": {
      "post": {
        "summary": "On-Air event created",
        "description": "A new On-Air event was created.\n\n**Event name:** `onair.event.created`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive deliveries for the specified On-Air event\n",
        "operationId": "onair.event.created",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.event.created"
                        ],
                        "description": "Discriminator — always `onair.event.created` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/OnAirWebhookEvent"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.event.created",
                "eventId": "0197f9a7-6fa6-7aa6-907b-8c9d0e1f2a3b",
                "timestamp": "2026-06-15T09:30:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                  "title": "Product Launch",
                  "slug": "product-launch",
                  "start": "2026-07-01T17:00:00Z",
                  "end": "2026-07-01T18:00:00Z",
                  "timeZone": "America/Los_Angeles",
                  "eventPageUrl": "https://ro.am/onair/product-launch",
                  "enableSEO": true,
                  "autoAdmit": false,
                  "disableRSVP": false,
                  "hosts": [
                    {
                      "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                      "name": "Alex Chen"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.event.updated": {
      "post": {
        "summary": "On-Air event updated",
        "description": "An On-Air event was updated.\n\n**Event name:** `onair.event.updated`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive deliveries for the specified On-Air event\n",
        "operationId": "onair.event.updated",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.event.updated"
                        ],
                        "description": "Discriminator — always `onair.event.updated` for this event."
                      },
                      "data": {
                        "$ref": "#/components/schemas/OnAirWebhookEvent"
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.event.updated",
                "eventId": "0197f9a8-70b7-7bb7-a18c-9d0e1f2a3b4c",
                "timestamp": "2026-06-22T11:12:13.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                  "title": "Product Launch (rescheduled)",
                  "slug": "product-launch",
                  "start": "2026-07-02T17:00:00Z",
                  "end": "2026-07-02T18:00:00Z",
                  "timeZone": "America/Los_Angeles",
                  "eventPageUrl": "https://ro.am/onair/product-launch",
                  "enableSEO": true,
                  "autoAdmit": false,
                  "disableRSVP": false,
                  "hosts": [
                    {
                      "id": "709b8a57-70bc-427a-b6f0-b16ba5297f8c",
                      "name": "Alex Chen"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.event.canceled": {
      "post": {
        "summary": "On-Air event canceled",
        "description": "An On-Air event was canceled. Only the event's `id` and `title` are sent.\n\n**Event name:** `onair.event.canceled`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive deliveries for the specified On-Air event\n",
        "operationId": "onair.event.canceled",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.event.canceled"
                        ],
                        "description": "Discriminator — always `onair.event.canceled` for this event."
                      },
                      "data": {
                        "type": "object",
                        "description": "The canceled event (identity only).",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The event's unique identifier."
                          },
                          "title": {
                            "type": "string",
                            "description": "Event title."
                          }
                        },
                        "required": [
                          "id",
                          "title"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.event.canceled",
                "eventId": "0197f9a9-81c8-7cc8-b29d-0e1f2a3b4c5d",
                "timestamp": "2026-06-25T16:45:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                  "title": "Product Launch"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.guest.rsvp": {
      "post": {
        "summary": "On-Air guest RSVP changed",
        "description": "A guest's RSVP status for an On-Air event changed.\n\n**Event name:** `onair.guest.rsvp`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive RSVPs for the specified event\n- `status`: one of `invited`, `going`, `maybe`, `notGoing` — only receive RSVPs that changed to this status\n",
        "operationId": "onair.guest.rsvp",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.guest.rsvp"
                        ],
                        "description": "Discriminator — always `onair.guest.rsvp` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "event": {
                            "type": "object",
                            "description": "The event the guest belongs to (identity only).",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The event's unique identifier."
                              },
                              "title": {
                                "type": "string",
                                "description": "Event title."
                              }
                            },
                            "required": [
                              "id",
                              "title"
                            ]
                          },
                          "guest": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The guest's unique identifier."
                              },
                              "email": {
                                "type": "string",
                                "format": "email",
                                "description": "Guest email. Omitted when unset."
                              },
                              "name": {
                                "type": "string",
                                "description": "Guest name. Omitted when unset."
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "invited",
                                  "going",
                                  "maybe",
                                  "notGoing"
                                ],
                                "description": "The guest's new RSVP status."
                              },
                              "previousStatus": {
                                "type": "string",
                                "enum": [
                                  "invited",
                                  "going",
                                  "maybe",
                                  "notGoing"
                                ],
                                "description": "The guest's prior RSVP status. Omitted when not known."
                              }
                            },
                            "required": [
                              "id",
                              "status"
                            ]
                          }
                        },
                        "required": [
                          "event",
                          "guest"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.guest.rsvp",
                "eventId": "0197f9aa-92d9-7dd9-83ae-1f2a3b4c5d6e",
                "timestamp": "2026-06-18T14:03:27.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "event": {
                    "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                    "title": "Product Launch"
                  },
                  "guest": {
                    "id": "af6663d5-0f37-4105-95df-4fea20ef7c7c",
                    "email": "jordan@example.com",
                    "name": "Jordan Smith",
                    "status": "going",
                    "previousStatus": "invited"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "onair.guest.added": {
      "post": {
        "summary": "On-Air guests added",
        "description": "One or more guests were added to an On-Air event.\n\n**Event name:** `onair.guest.added`\n\n**Required scope:** `onair:read`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\n**Filter options:**\n- `eventId`: UUID — only receive additions for the specified event\n",
        "operationId": "onair.guest.added",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "onair.guest.added"
                        ],
                        "description": "Discriminator — always `onair.guest.added` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "event": {
                            "type": "object",
                            "description": "The event the guests were added to (identity only).",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The event's unique identifier."
                              },
                              "title": {
                                "type": "string",
                                "description": "Event title."
                              }
                            },
                            "required": [
                              "id",
                              "title"
                            ]
                          },
                          "guests": {
                            "type": "array",
                            "description": "The guests that were added (non-empty).",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "description": "The guest's unique identifier."
                                },
                                "email": {
                                  "type": "string",
                                  "format": "email",
                                  "description": "Guest email. Omitted when unset."
                                },
                                "name": {
                                  "type": "string",
                                  "description": "Guest name. Omitted when unset."
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "invited",
                                    "going",
                                    "maybe",
                                    "notGoing"
                                  ],
                                  "description": "The guest's RSVP status."
                                }
                              },
                              "required": [
                                "id",
                                "status"
                              ]
                            }
                          }
                        },
                        "required": [
                          "event",
                          "guests"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "onair.guest.added",
                "eventId": "0197f9ab-a3ea-7eea-94bf-2a3b4c5d6f70",
                "timestamp": "2026-06-16T10:15:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "event": {
                    "id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
                    "title": "Product Launch"
                  },
                  "guests": [
                    {
                      "id": "af6663d5-0f37-4105-95df-4fea20ef7c7c",
                      "email": "jordan@example.com",
                      "name": "Jordan Smith",
                      "status": "invited"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to indicate that the data was received successfully"
          }
        }
      }
    },
    "token.revoked": {
      "post": {
        "summary": "Token revoked",
        "description": "Fires when an OAuth access token belonging to your app is revoked by any\npath: the user, an admin, your own `token.revoke` call, or SCIM user\ndeactivation.\n\n**Event name:** `token.revoked`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nDelivery is authenticated by your webhook signing secret — not by the\nrevoked token — so you still receive this event after the grant is gone.\nYou may also receive it for a token you no longer hold (for example after\ncalling `token.revoke` yourself); treat handling as idempotent.\n\nThis event does **not** mean the app was uninstalled. Webhook subscriptions\nand other users' tokens are untouched unless a separate uninstall path\nalso runs. See [`app.uninstalled`](/docs/webhooks/app-uninstalled).\n\n**Required scope:** `webhook:write` (the same scope needed to subscribe).\n",
        "operationId": "token.revoked",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "token.revoked"
                        ],
                        "description": "Discriminator — always `token.revoked` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "clientId": {
                            "type": "string",
                            "description": "Your OAuth app's client ID."
                          },
                          "personId": {
                            "type": "integer",
                            "format": "int64",
                            "description": "Present for personal-mode tokens — the authorizing\nperson's ID. Omitted for organization-mode tokens.\n"
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "user_revoked",
                              "admin_revoked",
                              "api_revoked",
                              "user_deactivated"
                            ],
                            "description": "Why the token was revoked:\n- `user_revoked` — end user revoked the connected app\n- `admin_revoked` — admin revoked a connection or rejected approval\n- `api_revoked` — your app called `token.revoke` (or v0 `app.uninstall`)\n- `user_deactivated` — the authorizing user was deactivated (e.g. SCIM)\n"
                          }
                        },
                        "required": [
                          "clientId",
                          "reason"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "token.revoked",
                "eventId": "0197f9b0-1a2b-7cc3-9d0e-1f2a3b4c5d6e",
                "timestamp": "2026-07-20T18:00:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "clientId": "9f3c2a1b0e8d7c6b5a49382716150493",
                  "personId": 12345,
                  "reason": "user_revoked"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    },
    "app.uninstalled": {
      "post": {
        "summary": "App uninstalled",
        "description": "Fires when your app's installation is removed from a Roam — for example an\nadmin revokes the installed app, or the developer archives the API client.\nRoam enqueues this delivery **before** tearing down your webhook\nsubscriptions and tokens, so you get a last-gasp signed callback.\n\n**Event name:** `app.uninstalled`\n\nDelivered inside the standard [event envelope](/docs/webhooks/webhooks#event-envelope);\nthe schema below shows the full delivery body with this event's payload\nunder `data`.\n\nDelivery is authenticated by your webhook signing secret. After this event,\ndiscard all tokens for the install and stop retrying API calls.\n\nCalling [`token.revoke`](/docs/api/token-revoke) does **not** fire\n`app.uninstalled` — that endpoint only kills the presented grant and fires\n[`token.revoked`](/docs/webhooks/token-revoked).\n\n**Required scope:** `webhook:write` (the same scope needed to subscribe).\n",
        "operationId": "app.uninstalled",
        "tags": [
          "events"
        ],
        "x-fern-webhook": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEnvelope"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "app.uninstalled"
                        ],
                        "description": "Discriminator — always `app.uninstalled` for this event."
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "clientId": {
                            "type": "string",
                            "description": "Your OAuth app's client ID."
                          },
                          "roamId": {
                            "type": "string",
                            "description": "Public external ID of the Roam the install was removed\nfrom, when known. Omitted when the uninstall is not\nroam-scoped (e.g. archiving the client globally).\n"
                          }
                        },
                        "required": [
                          "clientId"
                        ]
                      }
                    }
                  }
                ]
              },
              "example": {
                "type": "app.uninstalled",
                "eventId": "0197f9b1-2b3c-7dd4-ae1f-2a3b4c5d6e7f",
                "timestamp": "2026-07-20T18:05:00.000000Z",
                "apiVersion": "2026-07-07",
                "data": {
                  "clientId": "9f3c2a1b0e8d7c6b5a49382716150493",
                  "roamId": "12QJUKKE0X"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return a 200 status to acknowledge receipt of the event"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Pass your API Key or OAuth access token as a Bearer token.\nExample: `Authorization: Bearer <token>`\n"
      }
    },
    "schemas": {
      "WebhookSubscriptionFilter": {
        "type": [
          "object",
          "null"
        ],
        "description": "Event-specific filter to limit webhook notifications. Different properties apply to different events.",
        "properties": {
          "chatType": {
            "type": "string",
            "enum": [
              "dm",
              "group"
            ],
            "description": "For `chat.message`: restrict to direct messages (`dm`) or group messages (`group`)."
          },
          "mention": {
            "type": "boolean",
            "description": "For `chat.message`: restrict to messages that @mention your app."
          },
          "names": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "For `chat.reaction`: restrict to events where the changed reaction is one of these names (e.g. 'thumbs_up', 'heart'), matching the `name` field of `/reaction.add` and `/reaction.list`."
          },
          "hasVideo": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "For `meeting.ended`: restrict to meetings that were video recorded, i.e. a video track exists. This keys on \"was recorded\", not \"is ready to fetch\" — the recording upload is a separate pipeline that has almost never finished when the event fires, so a matching delivery normally arrives while the upload is still in flight. Call `/meeting.info` and read `videoStatus` to learn when the recording is playable. Only `true` is accepted — `{\"hasVideo\": false}` is rejected at subscribe time rather than silently treated as no filter, so omit the filter to receive every `meeting.ended` event."
          },
          "eventId": {
            "type": "string",
            "description": "For On-Air events (`onair.event.*`, `onair.guest.*`): restrict to the specified event."
          },
          "status": {
            "type": "string",
            "description": "For `onair.guest.rsvp`: restrict to the specified RSVP status.",
            "enum": [
              "invited",
              "going",
              "maybe",
              "notGoing"
            ]
          }
        },
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Always `false` on error responses."
          },
          "error": {
            "type": "string",
            "description": "Machine-readable error code from the catalog\n(e.g. `invalid_token`, `missing_scope`, `ratelimited`, `invalid_cursor`).\nBranch on this field. See [Responses and Errors](/docs/guides/responses-and-errors).\n"
          },
          "needed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Present on `missing_scope`. Scopes that would satisfy the check.\n**Any-of (OR)** semantics: holding any one element is enough.\nDistinct from Slack's comma-separated `needed` string.\n"
          },
          "provided": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Present on `missing_scope`. The token's granted scopes after alias\nnormalization (e.g. legacy `groups:read` reports as `group:read`).\nFor personal tokens this is the expanded OAuth set, not `pat:*`\ngroup names.\n"
          }
        },
        "required": [
          "ok",
          "error"
        ]
      },
      "WebhookSubscriptionRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 1024,
            "description": "Destination URL for webhook deliveries (max 1024 characters). HTTPS is required outside local environments."
          },
          "event": {
            "type": "string",
            "description": "Event to subscribe to.",
            "enum": [
              "chat.message",
              "chat.reaction",
              "chat.link.shared",
              "lobby.booked",
              "magicast.created",
              "meeting.started",
              "meeting.ended",
              "user.status.update",
              "onair.event.created",
              "onair.event.updated",
              "onair.event.canceled",
              "onair.guest.rsvp",
              "onair.guest.added",
              "token.revoked",
              "app.uninstalled"
            ]
          },
          "filter": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilter"
          },
          "apiVersion": {
            "type": "string",
            "description": "Optional [API version](/docs/guides/api-versioning) (`YYYY-MM-DD`) to pin\nthis subscription's payload shape to. When omitted, the subscription is\nfrozen at your integration's default version. Unsupported values return\n`400`.\n"
          }
        },
        "required": [
          "url",
          "event"
        ]
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier of the webhook subscription."
          },
          "event": {
            "type": "string",
            "description": "Subscribed event name.",
            "enum": [
              "chat.message",
              "chat.reaction",
              "chat.link.shared",
              "lobby.booked",
              "magicast.created",
              "meeting.started",
              "meeting.ended",
              "user.status.update",
              "onair.event.created",
              "onair.event.updated",
              "onair.event.canceled",
              "onair.guest.rsvp",
              "onair.guest.added",
              "token.revoked",
              "app.uninstalled"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Destination URL for webhook deliveries."
          },
          "filter": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilter",
            "description": "Event-specific filter applied to the subscription."
          },
          "dynamic": {
            "type": "boolean",
            "description": "`true` if the subscription was created via `/webhook.subscribe`.\n`false` if it was configured statically in the Roam Administration UI.\n"
          },
          "created": {
            "type": "string",
            "format": "date-time",
            "description": "When the subscription was created."
          },
          "lastSuccessAt": {
            "type": "string",
            "format": "date-time",
            "description": "Last terminal 2xx (RFC3339 UTC). Omitted until the destination has\nsucceeded at least once. Kept when a pause is cleared.\n"
          },
          "failStreakStartedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Start of the current consecutive-failure span (RFC3339 UTC). Cleared\non 2xx and on [`/webhook.subscribe`](/docs/webhooks/webhook-subscribe)\nto the same event+URL. Omitted when healthy.\n"
          },
          "disabledAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the fail streak reached 24 hours (RFC3339 UTC). While set the\nsubscription is paused (one probe event per day). Omitted when active.\nSee [Subscription health](/docs/webhooks/webhooks#subscription-health).\n"
          }
        },
        "required": [
          "id",
          "event",
          "url",
          "dynamic"
        ]
      },
      "WebhookUnsubscribeRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifier of the webhook subscription to remove."
          }
        },
        "required": [
          "id"
        ]
      },
      "WebhookEnvelope": {
        "type": "object",
        "description": "The common envelope of every v1 webhook delivery. Every event body carries\nthe same top-level fields, so one handler can route on `type` and\nde-duplicate on `eventId` before dispatching to event-specific parsing of\n`data`.\n",
        "properties": {
          "type": {
            "type": "string",
            "description": "The full event name (e.g. `chat.message`), identical to the\nsubscription's `event` and the `Roam-Event-Type` delivery header.\n"
          },
          "eventId": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier of the event occurrence. Retries of the same event —\nand deliveries of the same event to your other subscriptions — carry the\nsame `eventId`, so use it as your idempotency/de-dupe key. It also\narrives as the `webhook-id` header.\n"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "When the event occurred, RFC3339 UTC with microsecond precision\n(e.g. `2026-07-07T18:23:45.123456Z`).\n"
          },
          "apiVersion": {
            "type": "string",
            "description": "The API version this body is rendered as (`YYYY-MM-DD`), determined by\nthe subscription's pinned version. Equals the `Roam-Version` delivery\nheader. See the [API versioning guide](/docs/guides/api-versioning).\n"
          },
          "data": {
            "type": "object",
            "description": "The event payload. Its shape is event-specific — see each event's page.\n"
          }
        },
        "required": [
          "type",
          "eventId",
          "timestamp",
          "apiVersion",
          "data"
        ]
      },
      "LinkSharedEventData": {
        "type": "object",
        "description": "Links from a newly created text message that match an app's registered domains.",
        "properties": {
          "chatId": {
            "type": "string",
            "format": "uuid"
          },
          "chatType": {
            "type": "string",
            "description": "Type of chat containing the message, such as `dm` or `group`."
          },
          "messageId": {
            "type": "string",
            "format": "uuid"
          },
          "messageTimestamp": {
            "type": "integer",
            "description": "Message timestamp in Unix microseconds."
          },
          "userId": {
            "type": "string",
            "format": "uuid",
            "description": "ID of the user who posted the message."
          },
          "links": {
            "type": "array",
            "minItems": 1,
            "description": "Unique matching URLs in order of first appearance.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Exact URL as it appears in the message."
                },
                "domain": {
                  "type": "string",
                  "description": "Parsed lowercase ASCII hostname."
                }
              },
              "required": [
                "url",
                "domain"
              ]
            }
          }
        },
        "required": [
          "chatId",
          "chatType",
          "messageId",
          "messageTimestamp",
          "userId",
          "links"
        ]
      },
      "LobbyConfiguration": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique identifier for the lobby configuration"
          },
          "slug": {
            "type": "string",
            "description": "URL-safe slug for the lobby"
          },
          "displayName": {
            "type": "string",
            "description": "Human-readable name of the lobby"
          },
          "active": {
            "type": "boolean",
            "description": "Whether the lobby is currently active"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL for the lobby"
          },
          "handle": {
            "type": "string",
            "description": "The handle extracted from the lobby URL, if available"
          }
        },
        "required": [
          "id",
          "slug",
          "displayName",
          "active",
          "url"
        ]
      },
      "LobbyBookingHost": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Display name of the host"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the host"
          },
          "isOrganizer": {
            "type": "boolean",
            "description": "Whether this host is the organizer"
          }
        },
        "required": [
          "email",
          "isOrganizer"
        ]
      },
      "LobbyBookingInvitee": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Display name of the invitee"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the invitee"
          },
          "status": {
            "type": "string",
            "description": "Invitee's RSVP or booking status"
          },
          "isBooker": {
            "type": "boolean",
            "description": "Whether this invitee created the booking"
          }
        },
        "required": [
          "email",
          "status"
        ]
      },
      "LobbyBookingResponse": {
        "type": "object",
        "description": "A guest's answer to one of the lobby's custom questions, captured when the booking\nwas made. Includes answers to hidden fields, which are populated from URL query\nparameters on the lobby link (e.g. `?utm_source=partner`).\n",
        "properties": {
          "fieldId": {
            "type": "string",
            "description": "ID of the custom field (question) this answer belongs to."
          },
          "key": {
            "type": "string",
            "description": "The field's stable key, if the lobby owner assigned one, as captured when the\nbooking was made. For hidden fields this is the URL query parameter name used to\npopulate the value.\n"
          },
          "question": {
            "type": "string",
            "description": "The question's display name. Omitted if the field definition can no longer be\nfound on the lobby configuration.\n"
          },
          "type": {
            "type": "string",
            "enum": [
              "short_text",
              "text",
              "email",
              "phone_number",
              "radio",
              "checkbox",
              "dropdown",
              "hidden"
            ],
            "description": "The custom field type. Omitted when `question` is omitted."
          },
          "value": {
            "description": "The human-readable answer. For option fields (radio, checkbox, dropdown) this is\nthe selected option label(s), not internal option IDs. Checkbox answers are\narrays of strings; all other answers are strings.\n",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          }
        },
        "required": [
          "fieldId"
        ]
      },
      "LobbyBooking": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique booking identifier"
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Start time in RFC3339"
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "End time in RFC3339"
          },
          "status": {
            "type": "string",
            "description": "Current status of the booking"
          },
          "timeZone": {
            "type": "string",
            "description": "IANA time zone of the booking times"
          },
          "notes": {
            "type": "string",
            "description": "Optional notes provided by the booker"
          },
          "created": {
            "type": "string",
            "format": "date-time",
            "description": "Creation time"
          },
          "hosts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LobbyBookingHost"
            }
          },
          "invitees": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LobbyBookingInvitee"
            }
          },
          "responses": {
            "type": "array",
            "description": "The guest's answers to the lobby's custom questions, including hidden fields\npopulated from URL query parameters on the lobby link. One entry per answered\nquestion; empty or absent when the guest answered no custom questions.\n",
            "items": {
              "$ref": "#/components/schemas/LobbyBookingResponse"
            }
          },
          "meetingLink": {
            "type": "string",
            "format": "uri",
            "description": "Meeting link URL, used to join the meeting"
          }
        },
        "required": [
          "id",
          "start",
          "end",
          "status"
        ]
      },
      "Magicast": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the magicast"
          },
          "name": {
            "type": "string",
            "description": "Display name of the magicast"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO-8601 timestamp when the magicast was created (UTC)"
          },
          "ownerId": {
            "type": "string",
            "format": "uuid",
            "description": "Address ID of the magicast owner"
          },
          "coverImageUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL for the magicast cover image thumbnail"
          }
        },
        "required": [
          "id",
          "name",
          "createdAt"
        ]
      },
      "MagicastChapter": {
        "type": "object",
        "description": "A navigation chapter generated from the Magicast transcript.",
        "properties": {
          "title": {
            "type": "string",
            "description": "Chapter title."
          },
          "startTime": {
            "type": "integer",
            "description": "Milliseconds from the start of the recording."
          }
        },
        "required": [
          "title",
          "startTime"
        ]
      },
      "MagicastCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Magicast"
          },
          {
            "type": "object",
            "description": "`magicast.created` payload. Metadata only — no `cues` and no signed\n`videoUrl`. Use [`/magicast.info`](/docs/api/magicast-info) for those.\n",
            "properties": {
              "durationMs": {
                "type": "integer",
                "description": "Duration of the playable video in milliseconds, when known."
              },
              "videoStatus": {
                "type": "string",
                "enum": [
                  "none",
                  "processing",
                  "available"
                ],
                "description": "Same values as [`/magicast.info`](/docs/api/magicast-info)."
              },
              "hasTranscript": {
                "type": "boolean",
                "description": "True when a transcript is available via `/magicast.info`."
              },
              "shareUrl": {
                "type": "string",
                "format": "uri",
                "description": "Player URL if a share link already exists. Omitted otherwise.\nThis event never mints a share link.\n"
              },
              "chapters": {
                "type": "array",
                "description": "Navigation chapters, when generated.",
                "items": {
                  "$ref": "#/components/schemas/MagicastChapter"
                }
              }
            }
          }
        ]
      },
      "MeetingParticipant": {
        "type": "object",
        "description": "A participant in a meeting",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "member",
              "guest"
            ],
            "description": "Whether the participant is a workspace member or an external guest"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The participant's address ID"
          },
          "name": {
            "type": "string",
            "description": "Display name of the participant"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the participant (requires `user:read.email` scope)"
          }
        },
        "required": [
          "type",
          "id",
          "name"
        ]
      },
      "ActionItem": {
        "type": "object",
        "description": "An AI-extracted action item from a meeting. `complete` reflects whether the\ntask has been marked done. Assignment has two forms: an explicit `assigneeId`\n(a user the item was assigned to) and a `suggestedAssigneeId` (an AI-suggested\nowner); `suggestedAssigneeName` is the display name for whichever applies.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Action item ID."
          },
          "title": {
            "type": "string",
            "description": "Action item title."
          },
          "description": {
            "type": "string",
            "description": "Longer description of the action item."
          },
          "complete": {
            "type": "boolean",
            "description": "Whether the action item has been marked complete. Omitted when false."
          },
          "assigneeId": {
            "type": "string",
            "format": "uuid",
            "description": "User ID this item was explicitly assigned to, if any."
          },
          "suggestedAssigneeId": {
            "type": "string",
            "format": "uuid",
            "description": "AI-suggested assignee user ID, if any."
          },
          "suggestedAssigneeName": {
            "type": "string",
            "description": "Display name for the assignee — resolved from `assigneeId` when set, otherwise the AI-suggested name."
          },
          "assignedToMe": {
            "type": "boolean",
            "description": "Whether this item is assigned to the authenticated user. Personal access tokens only."
          },
          "suggestedForMe": {
            "type": "boolean",
            "description": "Whether this item is AI-suggested for the authenticated user. Personal access tokens only."
          }
        },
        "required": [
          "title"
        ]
      },
      "User": {
        "type": "object",
        "description": "A v1 acting principal. Workspace members and guests have `type: user`;\nclassic bots, agents, assistants, and coworkers have `type: bot`.\nSee [Identity & Principals](/docs/guides/identity-and-principals).\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The principal's unique address identifier."
          },
          "type": {
            "type": "string",
            "enum": [
              "user",
              "bot"
            ],
            "description": "Stable public principal type. All automated actors are `bot`."
          },
          "name": {
            "type": "string",
            "description": "Display name of the principal."
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL of the principal's profile image."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address for a member or guest (requires `user:read.email`). Omitted for bots."
          },
          "isGuest": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Present and true only for users without membership in the caller's account."
          },
          "isAdmin": {
            "type": "boolean",
            "description": "Whether a workspace member is an admin. Present for members even when false; omitted for guests and bots."
          },
          "jobTitle": {
            "type": "string",
            "description": "Workspace member's job title. Omitted for guests and bots."
          },
          "location": {
            "type": "string",
            "description": "Workspace member's location. Omitted for guests and bots."
          },
          "status": {
            "type": "string",
            "enum": [
              "checkedIn",
              "checkedOut"
            ],
            "description": "User's current presence status. Only included when `expand=status` is requested and the `user:read.status` scope is granted."
          },
          "willReturn": {
            "type": "object",
            "description": "Out-of-office / \"Will Return\" status. Present only when `expand=status` is requested, the `user:read.status` scope is granted, and the user has a future return time. A user can be `checkedIn` and still have `willReturn` (multi-day Out of Roam) — key off the presence of this object rather than `status` alone.",
            "properties": {
              "returnTime": {
                "type": "string",
                "format": "date-time",
                "description": "When the user is expected to return (RFC 3339)."
              },
              "reason": {
                "type": "string",
                "description": "Optional absence message (e.g. \"On Vacation\")."
              },
              "outOfRoam": {
                "type": "boolean",
                "description": "When true, multi-day Out of Roam that persists across check-ins. When false or omitted, same-day Will Return Today."
              }
            },
            "required": [
              "returnTime"
            ]
          },
          "available": {
            "type": "boolean",
            "description": "Whether the user is currently available for visitors. Only included when `expand=available` is requested and the `user:read.status` scope is granted."
          },
          "botCode": {
            "type": "string",
            "description": "Classic bot persona identifier, when available."
          },
          "integrationId": {
            "type": "string",
            "description": "Integration/client identifier for an automated actor, when available."
          }
        },
        "required": [
          "id",
          "type",
          "name"
        ]
      },
      "OnAirWebhookEvent": {
        "type": "object",
        "description": "An On-Air event, as delivered in the `onair.event.created` and `onair.event.updated` webhook payloads.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The event's unique identifier."
          },
          "title": {
            "type": "string",
            "description": "Event title."
          },
          "description": {
            "type": "string",
            "description": "Event description. Omitted when unset."
          },
          "slug": {
            "type": "string",
            "description": "URL slug for the event page."
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Event start time (RFC-3339)."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "Event end time (RFC-3339)."
          },
          "timeZone": {
            "type": "string",
            "description": "IANA timezone for the event (e.g. `America/Los_Angeles`)."
          },
          "eventPageUrl": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of the event page."
          },
          "enableSEO": {
            "type": "boolean",
            "description": "Whether the event page is search-indexable."
          },
          "autoAdmit": {
            "type": "boolean",
            "description": "Whether guests are automatically admitted."
          },
          "disableRSVP": {
            "type": "boolean",
            "description": "Whether RSVPs are disabled for the event."
          },
          "hosts": {
            "type": "array",
            "description": "Event hosts (may be empty).",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "The host's user ID."
                },
                "name": {
                  "type": "string",
                  "description": "Host display name. Omitted when unset."
                },
                "imageUrl": {
                  "type": "string",
                  "format": "uri",
                  "description": "Host avatar URL. Omitted when unset."
                }
              },
              "required": [
                "id"
              ]
            }
          }
        },
        "required": [
          "id",
          "title",
          "slug",
          "start",
          "end",
          "timeZone",
          "eventPageUrl",
          "enableSEO",
          "autoAdmit",
          "disableRSVP",
          "hosts"
        ]
      }
    },
    "responses": {
      "Error": {
        "description": "Error response. `ok` is always false; branch on the machine-readable `error` code. See the Responses and Errors guide.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing_scope": {
                "summary": "Scope gate failure with re-auth details",
                "value": {
                  "ok": false,
                  "error": "missing_scope",
                  "needed": [
                    "chat:send_message",
                    "chat:write"
                  ],
                  "provided": [
                    "chat:read",
                    "chat:history",
                    "group:read"
                  ]
                }
              },
              "invalid_token": {
                "summary": "Unknown, malformed, or expired token",
                "value": {
                  "ok": false,
                  "error": "invalid_token"
                }
              }
            }
          }
        }
      }
    }
  },
  "x-fern-webhook-signature": {
    "type": "hmac",
    "header": "webhook-signature",
    "algorithm": "sha256",
    "encoding": "base64",
    "signature-prefix": "v1,",
    "payload-format": {
      "components": [
        "message-id",
        "timestamp",
        "body"
      ],
      "delimiter": "."
    },
    "timestamp": {
      "header": "webhook-timestamp",
      "format": "unix-seconds",
      "tolerance": 300
    },
    "message-id": {
      "header": "webhook-id"
    }
  }
}