Agent skill

Meeting BaaS Webhooks Skill

Receive and verify Meeting BaaS webhooks (meeting-recording bot API for Zoom, Google Meet and Teams, delivered via Svix). Use when setting up Meeting BaaS webhook handlers, debugging svix-signature verification, or handling events like bot.completed, bot.status_change, bot.chat_message, bot.failed and calendar.event_created.

Install this skill

npx skills add hookdeck/webhook-skills --skill meeting-baas-webhooks


Meeting BaaS is a meeting-recording bot API for Zoom, Google Meet and Microsoft Teams. In API v2, account-level webhooks report bot status, completion, chat and failure plus calendar sync events. They are delivered and signed by Svix, which implements Standard Webhooks. Every delivery is an HTTP POST with a JSON body and three signature headers: svix-id, svix-timestamp, and svix-signature. The body is {event, data}: the event name is the top-level event, and data.extra echoes the extra you set on the bot.

Only Meeting BaaS v2 account-level webhooks are supported. Per-bot callbacks and v1 API webhooks aren't signed this way and fail verification.

Per-bot callbacks (callback_config when you create a bot) are a separate channel: direct, unsigned requests that only carry an optional x-mb-secret header. This skill does not cover them.

When to Use This Skill

  • How do I receive Meeting BaaS webhooks?
  • How do I verify Meeting BaaS webhook signatures (svix-signature)?
  • How do I handle bot.completed, bot.status_change or bot.failed events?
  • How do I process bot.chat_message or calendar.event_created events?
  • Why is my Meeting BaaS webhook signature verification failing, or rejecting Hookdeck retries?

Verification (core)

Signed content is {svix-id}.{svix-timestamp}.{raw body}. Svix signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use the official svix library, which compares in constant time. Read the svix-* headers, and when svix-id is absent fall back to the webhook-* headers (webhook-id, webhook-timestamp, webhook-signature), as Hookdeck does.

import { Webhook } from 'svix'; // svix 2.x is ESM-only

const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.MEETING_BAAS_WEBHOOK_SECRET).verify(rawBody, {
  'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
  'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["MEETING_BAAS_WEBHOOK_SECRET"])  # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
                     "webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

The signing secret is per endpoint. Meeting BaaS's docs print no secret, but because v2 webhooks go through Svix it is expected to be a Svix whsec_<base64> value, and the examples assume that. Keep the full whsec_... value in MEETING_BAAS_WEBHOOK_SECRET. Handlers fail closed: with the variable unset (or not a whsec_ value) they return 500 instead of accepting anything. Missing headers or a bad signature get a 400.

Hookdeck retries and replays hit the 5-minute window

The svix library always enforces a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on svix-id instead of relying on the timestamp.

Common Event Types

12 events are documented (the list may not be exhaustive). The name is event in the JSON body. The examples handle all of these and return 200 for anything else:

EventTriggered When
bot.status_changeA bot's status changes (data.status.code, e.g. in_call_recording)
bot.completedA bot finished recording and processing (presigned artifact URLs, valid 4 hours)
bot.chat_messageA participant posts in the meeting chat (extra is beside data)
bot.failedA bot failed (data.error_code, data.error_message)
calendar.connection_createdA calendar connection was created
calendar.connection_updatedA calendar connection was updated
calendar.connection_deletedA calendar connection was deleted
calendar.connection_errorA calendar connection hit an error, e.g. OAuth refresh failed
calendar.events_syncedA calendar's events were synced (first sync)
calendar.event_createdAn event was created in a connected calendar
calendar.event_updatedAn event was updated in a connected calendar
calendar.event_cancelledAn event was cancelled in a connected calendar

See references/overview.md for payload shapes and delivery rules.

Environment Variables

# Signing secret of the webhook endpoint (Svix format, starts with whsec_)
MEETING_BAAS_WEBHOOK_SECRET=whsec_xxxxx

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 meeting-baas --path /webhooks/meeting-baas

Add the tunnel URL as a webhook endpoint in your Meeting BaaS account settings (not via the API) and copy its signing secret. See references/setup.md.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 12, 2026

View on GitHub →