MeetingRouter

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

  1. Validate webhook signatures: Always verify webhook authenticity

  2. Return 200 quickly: Process webhooks asynchronously

  3. Implement idempotency: Handle duplicate webhook deliveries

  4. Use type guards: Ensure type safety when processing events

  5. Error handling: Log errors but don't expose internal details

  6. 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);
}

On this page