# Webhooks v0 Overview

Legacy `/v0` Webhooks API (Alpha). Prefer [v1 Webhooks](/docs/webhooks/webhooks) for new integrations.

> **Legacy (`/v0`).** New integrations should use [v1 Webhooks](/docs/webhooks/webhooks)
> (`https://api.ro.am/v1`). This Alpha surface remains supported for existing callers but
> is frozen — see [Previous Versions](/docs/previous-versions) and the
> [Migration Guide](/docs/guides/migration-v0-to-v1).

The Roam HQ Events API (Alpha) delivers real-time notifications to your application via webhooks. Subscribe to events and receive HTTP callbacks when things happen in your Roam workspace.

**OpenAPI Spec:** [webhooks.json](https://developer.ro.am/webhooks.json)

## Configuring Webhooks

You can configure webhooks in two ways:
- **Static:** In **Roam Administration > Developer > API Client**, add webhook URLs directly to your app configuration
- **Dynamic:** Use the subscription endpoints below to manage webhooks programmatically

Destination URLs may be up to **1024 characters** (static config and
`webhook.subscribe`). Longer values are rejected with HTTP `400` /
`invalid_parameter`.

## Subscription Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| [`/webhook.subscribe`](/docs/webhooks-v0-dev/webhook-subscribe) | POST | Create or update a webhook subscription |
| [`/webhook.unsubscribe`](/docs/webhooks-v0-dev/webhook-unsubscribe) | POST | Remove a webhook subscription |

## Available Events

| Event | Description |
|-------|-------------|
| [`chat:message:dm`](/docs/webhooks-v0-dev/chat-message) | Direct message received by your app |
| [`chat:message:channel`](/docs/webhooks-v0-dev/chat-message) | Channel message in a group where your app is a member |
| [`chat:message:mention`](/docs/webhooks-v0-dev/chat-message) | Message that @mentions your app |
| [`chat:message:reaction`](/docs/webhooks-v0-dev/chat-message-reaction) | Emoji reaction added to a message |
| [`recording:saved`](/docs/webhooks-v0-dev/recording-saved) | Meeting recording is ready for download |
| [`transcript:started`](/docs/webhooks-v0-dev/transcript-started) | Magic Minutes has started on a meeting |
| [`transcript:saved`](/docs/webhooks-v0-dev/transcript-saved) | Meeting transcript (Magic Minutes) is available |
| [`lobby:booked`](/docs/webhooks-v0-dev/lobby-booked) | New booking created for a lobby |
| [`user:status:update`](/docs/webhooks-v0-dev/user-status-update) | User checked in or out of the Roam |
| [`onair.event.created`](/docs/webhooks-v0-dev/onair-event-created) | On-Air event is created |
| [`onair.event.updated`](/docs/webhooks-v0-dev/onair-event-updated) | On-Air event is updated |
| [`onair.event.canceled`](/docs/webhooks-v0-dev/onair-event-canceled) | On-Air event is canceled |
| [`onair.guest.rsvp`](/docs/webhooks-v0-dev/onair-guest-rsvp) | Guest RSVP status changed |
| [`onair.guest.added`](/docs/webhooks-v0-dev/onair-guest-added) | Guest(s) added to an event |
| [`magicast.created`](/docs/webhooks-v0-dev/magicast-created) | A magicast is created and ready |

## Access Models

Webhooks support both [Organization access and Personal access](/docs/guides/access-models). The same events are available in both modes, but the scope differs:

- **Organization access:** Webhooks deliver events for all activity across the workspace (e.g. all messages in public groups, all meeting transcripts).
- **Personal access:** Webhooks deliver only events involving the authenticated user (e.g. only your DMs, only your meeting transcripts).

See the [Access Models guide](/docs/guides/access-models) for details on choosing the right model.

## Webhook Payload Format

All webhooks are delivered as POST requests with JSON payloads. Your endpoint should return a 2xx status to acknowledge receipt.

## Delivery retries

Delivery is **best-effort with bounded in-memory retries**:

- Up to **4 HTTP attempts** (1 initial + 3 retries) for **transient**
  failures: network/transport errors, HTTP `5xx`, and HTTP `429`.
- Each attempt must return a 2xx within **3 seconds** or it counts as failed.
- The retries are near-immediate (about a second), then about **+1 minute**,
  then about **+5 minutes** — roughly a six-minute window end to end, matching
  Slack's Events API ladder.
- Retries carry `Roam-Retry-Num` (`1`, `2`, or `3`) and `Roam-Retry-Reason`
  (`transport`, `http_5xx`, or `http_429`); the initial attempt carries
  neither. `webhook-id` is identical on every attempt of the same event —
  de-duplicate on it.
- `Retry-After` on a `429` or `503` is honored in place of the next wait,
  capped at 5 minutes.
- Non-retryable client errors (`4xx` other than 429) fail immediately.
- Pending retries are held in memory only: a Roam process restart drops them,
  and a bounded pending-retry set sheds them under extreme load.
- If all attempts fail, the event is dropped for that delivery; the
  subscription is kept — repeated failures never auto-disable it — and the
  next event is attempted fresh. This is **not** a durable outbox — design
  endpoints to be idempotent and available.

## Signature Verification

Webhooks are signed using the [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification. Each request includes three headers for verification:

| Header | Description |
|--------|-------------|
| `webhook-id` | Unique identifier for this webhook delivery |
| `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
| `webhook-signature` | HMAC-SHA256 signature of the payload |

Your **Webhook Signing Secret** is available in **Roam Administration > Developer > API Client**.

Every API client type that can receive webhooks is issued a signing secret:
**API Key**, **OAuth**, and **Personal Access Token** apps all get a
`whsec_…` secret (one secret per app, shared by every install — Slack-style).
OAuth apps previously shipped without a secret and delivered unsigned payloads;
new and backfilled OAuth clients now sign deliveries the same way as API Key
clients.
Always verify the Standard Webhooks headers; reject deliveries that fail
signature verification.

To verify a webhook:
1. Concatenate: `{webhook-id}.{webhook-timestamp}.{payload}`
2. Compute HMAC-SHA256 using your signing secret (base64-decoded)
3. Compare with the signature in `webhook-signature` header

We recommend using the [standard-webhooks client libraries](https://github.com/standard-webhooks/standard-webhooks#libraries) for verification:

```javascript
import { Webhook } from "standardwebhooks";

const wh = new Webhook(signingSecret);
const payload = wh.verify(requestBody, requestHeaders);
```

## Delivery Behavior

- **Timeout**: Webhook requests timeout after **3 seconds**. Ensure your endpoint responds quickly.
- **Retries**: Webhooks are **not automatically retried**. If your endpoint returns a non-2xx status or times out, the delivery is logged but not reattempted.
- **Order**: Webhooks are delivered asynchronously and may arrive out of order.

For reliable processing, we recommend:
- Acknowledge webhooks immediately with a 200 response
- Process webhook data asynchronously in a background job
- Use the `webhook-id` header for idempotency

## Filtering

Some events support filters to limit notifications:
- `lobby:booked`: Filter by `lobbyId` to receive bookings for specific lobbies only
- `chat:message:reaction`: Filter by `codes` to only fire when a message carries a matching reaction. This gates delivery only — the payload still contains the message's full `reactions` list, so inspect `reactions[].code` in your handler to act on specific reaction types
- `onair.event.created`, `onair.event.updated`, `onair.event.canceled`, `onair.guest.added`: Filter by `eventId` to receive notifications for a specific event only
- `onair.guest.rsvp`: Filter by `eventId` and/or `status` to receive notifications for a specific event or RSVP status

## Authentication

```
Authorization: Bearer YOUR_TOKEN
```

## Base URL

```
https://api.ro.am/v0
```

---
Have questions? Contact us via [Roam Support Chat](https://ro.am/support/contact-us) or email [developer@ro.am](mailto:developer@ro.am).


## Base URL

```
https://api.ro.am/v0
```

## Authentication

```
Authorization: Bearer YOUR_API_KEY
```

## All Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | [`/webhook.subscribe`](/docs/webhooks-v0-dev/webhook-subscribe) | Subscribe to an event webhook |
| POST | [`/webhook.unsubscribe`](/docs/webhooks-v0-dev/webhook-unsubscribe) | Unsubscribe from an event webhook |

## Contact

- Email: developer@ro.am
- Website: https://developer.ro.am

---

*Machine-readable API documentation.*
*Full documentation: https://developer.ro.am/docs/webhooks-v0-dev*
