Webhooks
Complete guide to webhooks in Meeting BaaS v2
Webhooks allow you to receive real-time notifications about bot and calendar events. Instead of polling the API, you can configure webhook endpoints that will receive HTTP POST requests when events occur.
Overview
Meeting BaaS v2 uses SVIX for webhook delivery, ensuring reliable delivery with retries and delivery status tracking.
Webhook Configuration
Webhooks are configured at the account level. You can set up webhook endpoints in your account settings to receive notifications for all bot and calendar events.
Callbacks vs Webhooks
Meeting BaaS v2 supports two notification mechanisms:
-
Webhooks (Account-level): Configured in your account settings, sent via SVIX. All events for all bots are sent to your configured webhook endpoints.
-
Callbacks (Bot-specific): Configured per-bot when creating a bot using the
callback_configparameter. Callbacks are direct HTTP requests (POST or PUT) sent to your specified URL when that specific bot completes or fails.
Key Differences:
- Webhooks: Account-level, sent via SVIX with signature verification, all events
- Callbacks: Bot-specific, direct HTTP requests, only for bot completion/failure events
You can use both webhooks and callbacks together - they serve different purposes and complement each other.
Webhook Security
All webhooks are signed using SVIX's signature verification. The signature is included in the svix-id, svix-timestamp, and svix-signature headers.
To verify webhooks, use SVIX's verification libraries or verify the signature manually using your webhook signing secret.
Bot Webhooks
bot.status_change
Triggered whenever a bot's status changes (e.g., from queued to joining_call, from joining_call to in_call_recording, etc.).
Use Cases:
- Track bot progress in real-time
- Update UI to show current bot state
- Trigger actions based on status changes
- Monitor bot lifecycle
Payload:
{
"event": "bot.status_change",
"data": {
"bot_id": "123e4567-e89b-12d3-a456-426614174000",
"event_id": "789e0123-e45b-67c8-d901-234567890abc",
"status": {
"code": "in_call_recording",
"created_at": "2025-01-15T10:30:00Z",
"start_time": 1736941800
},
"extra": {
"customer_id": "12345"
}
}
}Status object fields:
code: The status code (see list below)created_at: ISO 8601 timestamp when this status change occurredstart_time(optional): Unix timestamp in seconds when recording started. Only present onin_call_recordingerror_message(optional): Human-readable description of the failure. Present onrecording_failedandmeeting_error, and may appear on other error states
Status Codes
The status.code field can contain any of the values below, grouped here by where they occur in the bot's lifecycle.
Lifecycle:
queued: Bot is queued and waiting to joinpickup_delayed: The bot has stayed in thequeuedstatus longer than the expected pickup window. No action is required on your end — this is informational. The bot may still proceed normally tojoining_call, and our team is automatically notified to investigate persistent occurrencestranscribing: Bot has exited and transcription is in progresscompleted: Bot has finished successfully (terminal state)failed: Bot has failed (terminal state)
In-call progress:
joining_call: Bot is attempting to join the meetingin_waiting_room: Bot is in the meeting's waiting room / lobbyin_waiting_for_host: Bot is waiting for the host to start or admit it (Zoom only)in_call_not_recording: Bot has joined the call but recording has not yet startedin_call_recording: Bot is in the meeting and recording. The payload includesstart_timerecording_paused: Recording has been paused (e.g., via the pause-recording endpoint)recording_resumed: Recording has resumed after a pausecall_ended: Bot has left the meetingrecording_succeeded: Recording finished and artifacts were captured successfullyrecording_failed: Recording could not be produced. The payload includeserror_message
Intermediary signals (Google Meet / Microsoft Teams) — these indicate why a recording is about to fail, and are followed by a recording_failed status and, ultimately, a bot.failed webhook:
api_request_stop: Bot was stopped via the leave-bot endpointbot_rejected: Bot was denied entry to the meeting (e.g., host rejected the join request)bot_removed: Bot was removed from the meeting by a participant after joiningbot_removed_too_early: Bot was removed before recording could startwaiting_room_timeout: Bot timed out waiting to be admitted from the waiting roominvalid_meeting_url: The meeting URL was invalid or could not be openedmeeting_error: A general meeting-related error occurred. The payload includeserror_message
If you only care about terminal outcomes, watch for completed and failed (or subscribe to the bot.completed and bot.failed webhooks instead). The other codes are useful for live progress tracking but are not required reading.
bot.completed
Triggered when a bot successfully completes recording and processing.
Use Cases:
- Download meeting recordings automatically
- Process transcriptions
- Trigger post-meeting workflows
- Update meeting records in your system
Payload:
{
"event": "bot.completed",
"data": {
"bot_id": "123e4567-e89b-12d3-a456-426614174000",
"event_id": "789e0123-e45b-67c8-d901-234567890abc",
"transcription": "https://s3.amazonaws.com/.../transcription.json",
"mp4": "https://s3.amazonaws.com/.../video.mp4",
"audio": "https://s3.amazonaws.com/.../audio.mp3",
"diarization": "https://s3.amazonaws.com/.../diarization.jsonl",
"chat_messages": "https://s3.amazonaws.com/.../chat_messages.json",
"duration_seconds": 3600,
"participants": [...],
"speakers": [...],
"extra": {
"customer_id": "12345"
}
}
}Note: All artifact URLs (transcription, mp4, audio, diarization) are presigned S3 URLs valid for 4 hours. For detailed information about each artifact type, see the Artifacts documentation.
bot.chat_message
Triggered in real-time when a chat message is received in the meeting. This allows you to process chat messages as they happen, without waiting for the meeting to end.
Use Cases:
- Build real-time chat integrations
- Respond to participant questions or commands
- Log chat messages for compliance
- Trigger workflows based on chat content
Payload:
{
"event": "bot.chat_message",
"data": {
"bot_id": "123e4567-e89b-12d3-a456-426614174000",
"event_id": "789e0123-e45b-67c8-d901-234567890abc",
"message_id": "spaces/XxM_aNTpzGkB/messages/1773539019302815",
"sender_name": "John Doe",
"sender_id": 2,
"text": "Can we discuss the Q4 roadmap?",
"sent_at": "2025-01-15T10:32:15Z"
},
"extra": {
"customer_id": "12345"
}
}Fields:
message_id: Unique identifier for the chat message (format varies by platform)sender_name: Display name of the message sendersender_id: Participant ID of the sender. May benullif the sender could not be resolved to a participanttext: Text content of the chat messagesent_at: ISO 8601 timestamp when this webhook was dispatched by the server (not when the message was sent in the meeting)
Note: The bot's own messages (sent via the send chat message endpoint) do not trigger this webhook — only messages from meeting participants are delivered. For a complete record of all chat messages (including bot-sent messages), use the chat_messages artifact available in the bot.completed webhook and bot details endpoint. See the Artifacts documentation for the artifact structure.
bot.failed
Triggered when a bot fails to complete successfully.
Use Cases:
- Handle errors gracefully
- Retry bot creation if appropriate
- Log failures for analysis
- Notify users of failures
Payload:
{
"event": "bot.failed",
"data": {
"bot_id": "123e4567-e89b-12d3-a456-426614174000",
"event_id": "789e0123-e45b-67c8-d901-234567890abc",
"error_code": "BOT_NOT_ACCEPTED",
"error_message": "Bot was not accepted into the meeting",
"extra": {
"customer_id": "12345"
}
}
}Common Error Codes:
BOT_NOT_ACCEPTED: Bot was not accepted into the meetingTIMEOUT_WAITING_TO_START: Meeting didn't start within the timeout periodINSUFFICIENT_TOKENS: Not enough tokens to create the botDAILY_BOT_CAP_REACHED: Daily bot creation limit reachedINVALID_MEETING_PLATFORM: Could not determine meeting platform from URLTRANSCRIPTION_ERROR: Error occurred during transcription
Calendar Webhooks
calendar.connection_created
Triggered when a new calendar connection is created.
Use Cases:
- Confirm calendar integration success
- Initialize calendar-specific workflows
- Track calendar connections
Payload:
{
"event": "calendar.connection_created",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"platform": "google",
"account_email": "user@example.com",
"calendar_name": "Primary"
}
}calendar.connection_updated
Triggered when a calendar connection is updated (e.g., OAuth credentials refreshed).
Use Cases:
- Track credential updates
- Monitor connection health
- Update connection status in your system
Payload:
{
"event": "calendar.connection_updated",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"platform": "google",
"status": "active"
}
}calendar.connection_deleted
Triggered when a calendar connection is deleted.
Use Cases:
- Clean up calendar-related data
- Notify users of disconnection
- Update UI to reflect removal
Payload:
{
"event": "calendar.connection_deleted",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"platform": "google"
}
}calendar.connection_error
Triggered when a calendar connection encounters an error (e.g., OAuth token refresh failed).
Use Cases:
- Alert users to connection issues
- Trigger automatic reconnection attempts
- Log errors for troubleshooting
Payload:
{
"event": "calendar.connection_error",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"platform": "google",
"error": "OAuth token refresh failed",
"status": "error"
}
}calendar.events_synced
Triggered after a calendar sync operation completes (initial sync).
Use Cases:
- Confirm sync completion
- Process newly synced events
- Update event cache
Payload:
{
"event": "calendar.events_synced",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"events_synced": 42,
"sync_type": "full"
}
}calendar.event_created
Triggered when a new event is created in a connected calendar.
Use Cases:
- Automatically schedule bots for new events
- Update event calendars
- Trigger event-specific workflows
Payload:
{
"event": "calendar.event_created",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"event_type": "one_off",
"series_id": "789e0123-e45b-67c8-d901-234567890abc",
"series_bot_scheduled": false,
"instances": [
{
"event_id": "abc123...",
"title": "Team Meeting",
"start_time": "2025-01-20T10:00:00Z",
"end_time": "2025-01-20T11:00:00Z",
"meeting_url": "https://meet.google.com/...",
"bot_scheduled": false
}
]
}
}calendar.event_updated
Triggered when an existing event is updated in a connected calendar.
Use Cases:
- Update bot schedules if meeting time changes
- Sync event changes to your system
- Handle event modifications
Payload:
{
"event": "calendar.event_updated",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"event_type": "recurring",
"series_id": "789e0123-e45b-67c8-d901-234567890abc",
"series_bot_scheduled": true,
"affected_instances": [
{
"event_id": "abc123...",
"title": "Team Meeting",
"start_time": "2025-01-20T10:00:00Z",
"end_time": "2025-01-20T11:00:00Z",
"meeting_url": "https://meet.google.com/...",
"bot_scheduled": true
}
]
}
}calendar.event_cancelled
Triggered when an event is cancelled in a connected calendar.
Use Cases:
- Cancel scheduled bots for cancelled events
- Update event status
- Clean up event-related data
Payload:
{
"event": "calendar.event_cancelled",
"data": {
"calendar_id": "123e4567-e89b-12d3-a456-426614174000",
"event_type": "one_off",
"series_id": "789e0123-e45b-67c8-d901-234567890abc",
"series_bot_scheduled": false,
"cancelled_instances": [
{
"event_id": "abc123...",
"title": "Team Meeting",
"start_time": "2025-01-20T10:00:00Z"
}
]
}
}Webhook Delivery
Retry Schedule
Failed webhook deliveries are automatically retried with increasing delays:
| Attempt | Delay | Cumulative Time |
|---|---|---|
| 1 (initial) | — | 0s |
| 2 | 5 seconds | ~5s |
| 3 | 10 seconds | ~15s |
| 4 | 5 minutes | ~5 min |
| 5 | 15 minutes | ~20 min |
| 6 | 30 minutes | ~50 min |
| 7 | 2 hours | ~3 hours |
All retries complete within approximately 3 hours. This is intentional — bot.completed webhooks include signed download URLs (for video, audio, transcription) that are valid for 4 hours. Keeping retries within this window ensures that URLs in the payload remain usable.
A small amount of random jitter (±20%) is added to each retry delay to prevent thundering herd issues.
Timeouts and Overload Protection
- Successful acknowledgement: Only HTTP
2xxresponses are treated as successful delivery. Any other status code — including3xxredirects,4xxclient errors, and5xxserver errors — is considered a failure and will be retried. Failed deliveries contribute to the retry schedule and, over time, to the auto-disable behavior. - Request timeout: Your endpoint must respond within 30 seconds. Requests that exceed this limit are treated as failures and retried.
- Overload penalty: If your endpoint returns a
429 Too Many Requestsstatus or times out, a minimum 60-second delay is applied before the next retry, regardless of the scheduled delay.
Idempotency and Ordering
- Idempotency: Webhooks may be delivered multiple times — ensure your handler is idempotent
- Ordering: Webhooks are generally delivered in order, but network issues or retries may cause out-of-order delivery
Endpoint Auto-Disable
If your webhook endpoint fails continuously for an extended period, it will be automatically disabled to prevent unnecessary retry traffic:
- After all retry attempts fail for a message, a
webhook_delivery_exhaustedoperational alert is emitted (if you have alert rules configured for this type) - After 5 days of continuous failure (no successful delivery across any message), the endpoint is automatically disabled
- When auto-disabled, the team owner receives an email notification with the endpoint details and common failure causes
- Your endpoint status is updated in the dashboard — you'll see it marked as disabled
- No new deliveries are attempted to a disabled endpoint until you manually re-enable it from the dashboard
Once an endpoint is auto-disabled, all subsequent webhook messages are silently dropped. Configure a Webhook Delivery Exhausted alert rule to get notified per-message as retries fail, well before the 5-day auto-disable threshold. See Alerts for setup instructions.
A single successful delivery at any point resets the failure clock — the 5-day timer only applies to endpoints that fail every delivery attempt without any success.
Testing Webhooks
You can test your webhook endpoint using tools like:
- ngrok for local development
- webhook.site or webhook.cool for testing
- SVIX CLI for local testing
Callbacks
Callbacks are bot-specific HTTP requests sent directly to a URL you provide when creating a bot. Unlike webhooks (which are account-level and sent via SVIX), callbacks are:
- Bot-specific: Configured per-bot using
callback_configwhen creating a bot - Direct HTTP: Sent directly to your URL (not via SVIX)
- Limited events: Only sent for
bot.completedandbot.failedevents - Same payload: Uses the same payload structure as webhooks
Configuring Callbacks
When creating a bot, include the callback_config in your request:
{
"bot_name": "My Bot",
"meeting_url": "https://meet.google.com/...",
"callback_enabled": true,
"callback_config": {
"url": "https://your-server.com/webhook",
"method": "POST",
"secret": "your-secret-key"
}
}Callback Security
If you provide a secret in callback_config, it will be included in the x-mb-secret header of all callback requests. Use this to verify that callbacks are coming from Meeting BaaS.
Callback Delivery and Retries
Callbacks are delivered with automatic retries on failure:
| Attempt | Delay |
|---|---|
| 1 (initial) | — |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 15 seconds |
- Only retries on network errors and 5xx server responses
- Does not retry on 4xx client errors (e.g., 400, 401, 403, 404)
- 30-second request timeout per attempt
If all 4 attempts fail, you can manually retry using the POST /v2/bots/:bot_id/retry-callback endpoint. This generates a fresh payload with new signed download URLs.
Resending Webhooks
If a webhook delivery fails, you can resend it using the POST /v2/bots/:bot_id/resend-webhook endpoint.
