MeetStream Guide: Webhook Events
MeetStream Guide: Webhook Events
This guide explains how to receive webhook events from MeetStream, what each event means, and how to trigger follow-up actions like fetching audio, video, or transcription.
Applies to: Google Meet, Zoom, Microsoft Teams.
Support: docs.meetstream.ai • API: api.meetstream.ai
1) Add your webhook URL while creating the bot
When you create a bot, include your webhook endpoint in the payload as:
MeetStream will send HTTP POST requests to your callback_url whenever the bot’s status changes during its lifecycle and when post-call processing completes.
Example (Create Bot + callback_url)
Docs: https://docs.meetstream.ai/api-reference/api-endpoints/bot-endpoints/create-bot
Tip: Your webhook should respond quickly with 2xx (e.g., 200) to acknowledge receipt.
2) Webhook payload format
Every webhook notification follows this structure:
Fields
bot_event— the event name. Identifies what just happened (bot.joining,bot.in_waiting_room,bot.inmeeting,bot.recording,bot.leaving,bot.stopped,bot.kicked,bot.denied,bot.notallowed,bot.failed,bot.done,audio.processed,transcription.processed,video.processed,data_deletion, etc.). Branch on this field.bot_id— unique identifier for the bot sessionbot_status— status detail (see Status Reference)message— human readable explanationstatus_code—200for success stages;500for any failure (Error,Denied,NotAllowed)timestamp— ISO-8601 timestampcustom_attributes— echoed back if you provided it when creating the bot
3) Event types (what you’ll receive)
MeetStream sends two categories of webhook events: bot lifecycle events and post-call processing events.
A) Bot lifecycle events
These events track the bot’s full lifecycle, from join to post-call completion:
Example payloads
bot.scheduled (scheduled bots only — fires immediately after POST /bots when join_at is set)
About
bot.scheduled: Confirms to webhook-driven integrations that the schedule was accepted. The bot will not actually join the meeting untilscheduled_join_time— at which pointbot.joiningand the normal lifecycle fire. Without subscribing to this event, the only signal that a scheduled bot was created is the 201 response body (or pollingGET /bots/{id}and checkingStatus: "Scheduled").
bot.joining (fired by the API server when the bot is dispatched)
bot.in_waiting_room
bot.inmeeting
bot.recording
bot.recording_permission_allowed (Zoom only)
bot.recording_permission_denied — denied by host (Zoom only)
bot.recording_permission_denied — host did not respond in time (Zoom only)
bot.leaving
Terminal-event payloads
Every terminal scenario fires a single webhook. Branch on bot_event to distinguish the cause.
Clean exit (bot.stopped)
Kicked by host/participant (bot.kicked)
Lobby timeout (bot.notallowed)
Host denied join (bot.denied)
Note: For Zoom recording-permission denial, the bot emits
bot.recording_permission_deniedfollowed bybot.leaving→bot.stopped— notbot.denied. See thebot.recording_permission_deniedexample above.
Unexpected failure (bot.failed)
bot.doneis the terminal event of the post-call pipeline — see Section 3.B below for the full payload, behavior, and its relationship withbot_status: "Done".
Important notes:
- Every bot starts with
bot.joining, fired by the MeetStream API server the moment the bot creation request is accepted and dispatched. For scheduled bots (join_at), the API server also firesbot.scheduledimmediately onPOST /botsto confirm the schedule was accepted;bot.joiningthen fires later when the scheduled execution time triggers. bot.in_waiting_roomis fired for every bot once the container clicks Join. For meetings without a lobby it fires briefly beforebot.inmeeting; for meetings with a host-admit lobby it lingers until the host admits.- If the bot joins successfully, you will receive
bot.inmeeting. bot.recordingis sent once the bot actually starts capturing audio/video. For Zoom this is gated on host-granted recording permission, so there can be a noticeable gap betweenbot.inmeetingandbot.recording. For Teams/GMeet,bot.recordingtypically fires within a second ofbot.inmeeting.- (Zoom only) Between
bot.inmeetingandbot.recording, you may receivebot.recording_permission_allowedonce the host grants recording permission. If the host denies the request OR does not respond within the configured timeout, you’ll receivebot.recording_permission_denied, followed by the usualbot.leaving→bot.stoppedterminal sequence (note: the terminal isbot.stopped, notbot.denied— recording-permission denial ends with a clean stop). bot.leavingis emitted briefly before any terminal event (whether normal exit, kick, denial, or failure).- The in-meeting lifecycle ends with exactly one terminal webhook:
bot.stopped/bot.kicked/bot.denied/bot.notallowed/bot.failed. - If the bot fails to join (lobby timeout/denied/error), you may get
bot.leavingthen a failure terminal event right afterbot.joiningwithout abot.inmeeting. - After the in-meeting terminal event, MeetStream runs the post-call pipeline (audio upload, video upload, transcription) and you’ll receive the artifact-specific events (
audio.processed,video.processed,transcription.processed) followed bybot.donewhen the pipeline completes. - Failure terminals (
bot.denied,bot.notallowed,bot.failed) carrystatus_code: 500andmessageprefixed with"Error: "or"Failed: "(see Section 4). Clean terminals (bot.stopped,bot.kicked) carrystatus_code: 200.
B) Post-call processing events
After the in-meeting terminal webhook, MeetStream continues processing the recorded session in the background. You will receive additional webhook events as each artifact finishes processing, plus a terminal bot.done once the pipeline is complete and data_deletion if/when the media is removed:
These events arrive asynchronously after the bot has stopped — the order and timing depend on processing duration for each artifact. The expected order on a healthy bot is roughly:
Statusfield progression during post-call: the liveStatuscolumn on the bot record (visible viaGET /bots/{id}) advances throughMediaProcessing(pipeline started) →Done(pipeline finished, matches thebot.donewebhook) →MediaExpired(media deleted, matchesdata_deletion).MediaProcessingis a status-only transition and does not have its own webhook — track the artifact-specific events instead.
Example payloads
audio.processed
transcription.processed
video.processed
bot.done — post-call pipeline finished
About
bot.done: Always carriesstatus_code: 200— it signals only that the post-call pipeline has finished, not whether each step succeeded. Inspect the artifact-specific events above (audio.processed,transcription.processed,video.processed,transcription.failed, etc.) for per-step success or failure.
data_deletion — media has been deleted (matches Status=MediaExpired)
About
data_deletion: Fires once when the bot’s media is removed from MeetStream’s storage — either when the customer callsDELETE /api/v1/bots/{bot_id}or when the retention window configured at bot-creation time elapses. After this event the bot’sStatuscolumn readsMediaExpiredand the artifact-fetch endpoints (GET /bots/{id}/audio,/video,/transcript) will return 404/410.
4) Status reference (bot_status)
bot_status gives more detail. The table below shows which bot_status value each event carries, alongside the bot_event name. Note that bot_status for bot.kicked is reported as "Stopped" (not "Kicked") — branch on bot_event to distinguish kicks from clean stops.
Failure messages
When a stage fails, status_code is 500 and message carries a prefix you
can grep on for alerting:
"Error: <details>"— unexpected exceptions (uncaught errors, AWS API failures)"Failed: <details>"— handled/expected failures (validation, timeout, denial)
Examples:
"Error: Failed to start recording""Failed: Recording permission denied by host""Failed: Not admitted to meeting""Error: Transcript processing failed"
5) Recommended actions based on webhook events
Your server can react to webhook events and run follow-up workflows.
A) On bot.inmeeting
Typical actions:
- Update your UI/state: “bot is live”
- Start timers / internal tracking
- Notify other systems that recording has started
B) On any terminal event (bot.stopped / bot.kicked / bot.denied / bot.notallowed / bot.failed)
This is the moment to mark the session as complete. Artifacts may still be processing at this point.
Recommended flow:
- Receive a terminal webhook
- Branch on
bot_event:bot.stopped— clean exit (meeting ended, API stop, host ended, etc.). Wait for post-call processing events before fetching outputs.bot.kicked— bot was forcibly removed by host/participant. Recording up to that point is still processed.bot.denied— host explicitly denied join. Log themessageand alert/handle accordingly.bot.notallowed— bot was not admitted (lobby/waiting-room timeout). Log and handle accordingly.bot.failed— unexpected error during the bot’s lifecycle. Logmessageand alert.
C) On post-call processing events
Once you receive audio.processed, transcription.processed, or video.processed, the corresponding artifact is ready to fetch:
- Audio: https://docs.meetstream.ai/api-reference/api-endpoints/bot-endpoints/get-bot-audio
- Video: https://docs.meetstream.ai/api-reference/api-endpoints/bot-endpoints/get-bot-video
D) On data_deletion
Confirms that bot data has been removed. Update your records accordingly — further fetch requests for this bot’s artifacts will fail.
6) Webhook signing (optional but recommended)
If you configure a webhook secret, MeetStream includes an HMAC signature in headers:
X-MeetStream-Signature:sha256=<hex_digest>X-MeetStream-Timestamp: ISO 8601 timestamp
Verification steps:
- Compute
HMAC-SHA256(your_secret, raw_request_body) - Compare with
X-MeetStream-Signature(stripsha256=prefix) - Optionally validate timestamp is within an acceptable window (replay protection)
7) Retry behavior (important)
- Webhook delivery is best-effort.
- If your endpoint returns a non-2xx, the webhook is not retried.
- A bot may send up to 3
bot.joiningevents if join retries are configured. bot.inmeetingis sent at most once. Exactly one terminal webhook (bot.stopped/bot.kicked/bot.denied/bot.notallowed/bot.failed) is sent per bot.- Post-call processing events (
audio.processed,transcription.processed,video.processed,bot.done,data_deletion) are sent at most once.
8) Minimal webhook handler checklist
- ✅ Accept
POSTrequests atcallback_url - ✅ Verify signature (if enabled)
- ✅ Always respond 2xx quickly
- ✅ Idempotent handling (store processed event IDs or de-dupe by
{bot_id, bot_event, timestamp}) - ✅ On any terminal event (
bot.stopped/bot.kicked/bot.denied/bot.notallowed/bot.failed), mark session complete - ✅ On
audio.processed/video.processed/transcription.processed, fetch the corresponding artifact - ✅ On
data_deletion, clean up local references
If you want, share your preferred stack (Node/FastAPI/Cloudflare Workers), and I’ll provide a ready-to-paste webhook handler example.
