Webhook Handling in v2 API
@meeting-baas/sdk - v2 API Reference / v2-webhook-handling
Webhook Handling in v2 API
The v2 API includes comprehensive TypeScript types for all webhook events, enabling type-safe webhook handlers.
Overview
Meeting BaaS sends webhooks to notify you about important events related to bots and calendar integrations. All webhook types are available through the V2 namespace.
Setup
Importing Webhook Types
import type { V2 } from "@meeting-baas/sdk";Configuring Webhook URL
Set your webhook URL when creating a bot:
const result = await client.createBot({
meeting_url: "https://meet.google.com/abc-def-ghi",
bot_name: "Meeting Assistant",
webhook_url: "https://your-server.com/webhooks/meeting-baas"
});Bot Webhooks
Bot Completed
Sent when a bot successfully completes recording a meeting:
import type { V2 } from "@meeting-baas/sdk";
async function handleWebhook(payload: V2.BotWebhookCompleted) {
if (payload.event === "bot.completed") {
console.log("Bot completed:", payload.data.bot_id);
// Access recording data
if (payload.data.mp4_url) {
console.log("Recording URL:", payload.data.mp4_url);
}
// Access transcription
if (payload.data.transcription) {
payload.data.transcription.forEach(segment => {
console.log(`${segment.speaker}: ${segment.text}`);
});
}
}
}Bot Failed
Sent when a bot fails:
import type { V2 } from "@meeting-baas/sdk";
async function handleWebhook(payload: V2.BotWebhookFailed) {
if (payload.event === "bot.failed") {
console.error("Bot failed:", payload.data.bot_id);
console.error("Error:", payload.data.error_message);
console.error("Error code:", payload.data.error_code);
}
}Bot Status Change
Sent when a bot's status changes:
import type { V2 } from "@meeting-baas/sdk";
async function handleWebhook(payload: V2.BotWebhookStatusChange) {
if (payload.event === "bot.status_change") {
console.log("Bot status changed:", {
botId: payload.data.bot_id,
oldStatus: payload.data.previous_status,
newStatus: payload.data.status
});
}
}Calendar Webhooks
Connection Events
import type { V2 } from "@meeting-baas/sdk";
// Connection created
async function handleConnectionCreated(
payload: V2.CalendarWebhookConnectionCreated
) {
if (payload.event === "calendar.connection.created") {
console.log("New calendar connected:", payload.data.calendar_id);
}
}
// Connection updated
async function handleConnectionUpdated(
payload: V2.CalendarWebhookConnectionUpdated
) {
if (payload.event === "calendar.connection.updated") {
console.log("Calendar updated:", payload.data.calendar_id);
}
}
// Connection deleted
async function handleConnectionDeleted(
payload: V2.CalendarWebhookConnectionDeleted
) {
if (payload.event === "calendar.connection.deleted") {
console.log("Calendar deleted:", payload.data.calendar_id);
}
}Event Webhooks
import type { V2 } from "@meeting-baas/sdk";
// Event created
async function handleEventCreated(
payload: V2.CalendarWebhookEventCreated
) {
if (payload.event === "calendar.event.created") {
console.log("New event:", payload.data.event_id);
console.log("Meeting URL:", payload.data.meeting_url);
console.log("Start time:", payload.data.start_time);
}
}
// Event updated
async function handleEventUpdated(
payload: V2.CalendarWebhookEventUpdated
) {
if (payload.event === "calendar.event.updated") {
console.log("Event updated:", payload.data.event_id);
}
}
// Event cancelled
async function handleEventCancelled(
payload: V2.CalendarWebhookEventCancelled
) {
if (payload.event === "calendar.event.cancelled") {
console.log("Event cancelled:", payload.data.event_id);
}
}
// Events synced
async function handleEventsSynced(
payload: V2.CalendarWebhookEventsSynced
) {
if (payload.event === "calendar.events.synced") {
console.log("Calendar synced:", payload.data.calendar_id);
console.log("Events synced:", payload.data.events_synced);
}
}Building a Webhook Handler
Type-Safe Handler
import type { V2 } from "@meeting-baas/sdk";
import express from "express";
const app = express();
app.use(express.json());
// Union type of all webhook payloads
type WebhookPayload =
| V2.BotWebhookCompleted
| V2.BotWebhookFailed
| V2.BotWebhookStatusChange
| V2.CalendarWebhookConnectionCreated
| V2.CalendarWebhookConnectionUpdated
| V2.CalendarWebhookConnectionDeleted
| V2.CalendarWebhookEventCreated
| V2.CalendarWebhookEventUpdated
| V2.CalendarWebhookEventCancelled
| V2.CalendarWebhookEventsSynced;
app.post("/webhooks/meeting-baas", async (req, res) => {
const payload = req.body as WebhookPayload;
try {
switch (payload.event) {
case "bot.completed":
await handleBotCompleted(payload);
break;
case "bot.failed":
await handleBotFailed(payload);
break;
case "bot.status_change":
await handleBotStatusChange(payload);
break;
case "calendar.connection.created":
await handleConnectionCreated(payload);
break;
// ... handle other events
default:
console.warn("Unknown event type:", (payload as any).event);
}
res.status(200).json({ received: true });
} catch (error) {
console.error("Webhook processing error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
async function handleBotCompleted(payload: V2.BotWebhookCompleted) {
// Process completed bot
console.log("Processing completed bot:", payload.data.bot_id);
// Save recording to database
if (payload.data.mp4_url) {
// await saveRecording(payload.data);
}
// Process transcription
if (payload.data.transcription) {
// await processTranscription(payload.data.transcription);
}
}
async function handleBotFailed(payload: V2.BotWebhookFailed) {
console.error("Bot failed:", {
botId: payload.data.bot_id,
error: payload.data.error_message,
code: payload.data.error_code
});
// Notify admin or retry
}
async function handleBotStatusChange(payload: V2.BotWebhookStatusChange) {
console.log("Bot status:", payload.data.status);
// Update UI or database
}Webhook Security
import crypto from "crypto";
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post("/webhooks/meeting-baas", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-webhook-signature"] as string;
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const data = JSON.parse(payload) as WebhookPayload;
// Process webhook...
});Callback URLs
For bot-specific callbacks (alternative to global webhook URL):
const result = await client.createBot({
meeting_url: "https://meet.google.com/abc-def-ghi",
bot_name: "Meeting Assistant",
callback_url: "https://your-server.com/callbacks/bot-123"
});Callback payload types:
import type { V2 } from "@meeting-baas/sdk";
// Success callback
type SuccessCallback = V2.CallbackCompleted;
// Failure callback
type FailureCallback = V2.CallbackFailed;Best Practices
-
Validate webhook signatures: Always verify webhook authenticity
-
Return 200 quickly: Process webhooks asynchronously
-
Implement idempotency: Handle duplicate webhook deliveries
-
Use type guards: Ensure type safety when processing events
-
Error handling: Log errors but don't expose internal details
-
Retry failed processing: Implement a queue for failed webhook processing
Example: Idempotent Processing
const processedWebhooks = new Set<string>();
async function processWebhook(payload: WebhookPayload) {
// Use webhook ID for idempotency
const webhookId = payload.webhook_id || `${payload.event}-${Date.now()}`;
if (processedWebhooks.has(webhookId)) {
console.log("Webhook already processed:", webhookId);
return;
}
// Process webhook
switch (payload.event) {
case "bot.completed":
await handleBotCompleted(payload);
break;
// ... other cases
}
// Mark as processed
processedWebhooks.add(webhookId);
}