Skip to navigation

Debugging Bots

Why didn't my bot join? Read the timeline, match the error, fix the cause
View as Markdown

When a bot doesn’t do what you expected, the answer is almost always in one of three places: the status timeline, the terminal webhook, or a timeout that fired. This page shows where to look and how to get from a symptom to a fix.

Start at the dashboard

The All Bots view lists every bot in your workspace with its platform, start time, duration and status.

All Bots page in the MeetStream dashboard with the date, platform and status filters, the search box, the Events button and a Not Allowed status badge highlighted
All Bots: filter by date, platform and status (1), search by bot ID, name or meeting URL (2), open the Bot Event Reference (3), and read each bot's status badge (4).
  1. Filters. Narrow by date, platform (Google Meet, Zoom, Teams, WhatsApp) and status.
  2. Search. Paste a bot_id, bot name or meeting URL.
  3. Events. Opens the Bot Event Reference, a quick glossary of webhook events. CSV exports the current list.
  4. Status badge. Hover it to see the raw API status. A Stopped bot shows as Completed, and a bot that was never admitted shows as Not Allowed.

Click any row to open its detail page.

Read the bot detail page

The bot detail page (Meeting timeline, transcript, and logs) shows everything MeetStream knows about one session.

Bot detail page for a completed Google Meet bot with the bot ID and Done badge, the meta bar, the recording preview and the status timeline highlighted
Top of the bot detail page: bot ID and current status (1), session facts (2), recording preview (3) and the Bot status timeline (4).
  1. Bot ID and status badge. The current status. While the bot is live, the top bar also offers Pause recording or Resume recording and a stop action.
  2. Meta bar. Started At, Stopped At, Bot Name, Meeting Code, Duration and Platform.
  3. Recording preview and Audio preview. Play the recording in the browser once it is processed.
  4. Bot status timeline. Every stage the bot went through, with a timestamp and the exact message. This is the same data as StatusTimeline in GET /api/v1/bots/{bot_id}/detail.

Scroll down to see the post-call stages and the downloads.

Bot status timeline showing Stopped, Processing Audio, Processing Video, Uploading and Done, the final status Done, and the Meeting Artifacts list with video, audio, transcript, screenshots and participants
Lower half of a successful session: the terminal status (1), post-call processing stages (2), the final status (3) and Meeting Artifacts (4).
  1. Terminal status. Here Stopped with Bot stopped via API request. The message tells you why the session ended.
  2. Post-call stages. Processing Audio, Processing Video, Uploading, and (if you requested a post-call transcript) Transcript Ready.
  3. Final status. Done means post-call processing finished. The Last event line repeats the latest stage.
  4. Meeting Artifacts. Download the video (.mp4) and audio (.wav), or view the transcript, screenshots and participants.

A healthy session reads: Joining, In Waiting Room, In Meeting, Recording, Leaving, Stopped, Processing Audio, Processing Video, Uploading, Done. In Waiting Room appears even when the host admits the bot instantly.

What a lobby timeout looks like

Bot detail page for a Google Meet bot with a Not Allowed badge, a timeline of Joining, In Waiting Room, Leaving and Not Admitted, and final status NotAllowed
A Google Meet bot that nobody admitted: Not Allowed badge (1), In Waiting Room (2), Not Admitted with Failed: Not admitted to meeting (3), and final status NotAllowed after about ten minutes (4).

The gap between In Waiting Room and Not Admitted equals the waiting_room_timeout (600 seconds by default on Google Meet). The timeline labels this stage Not Admitted; the API status is NotAllowed. For the fix, see WaitingRoomTimeout and Google Meet lobby troubleshooting.

The tabs below the timeline

TabWhat it shows
TranscriptSearch and review the full transcript, when a post-call provider was set.
ScreenshotScreenshots captured during the session (not available on Zoom).
ParticipantsPeople who joined the meeting.
Participant ActionsJoin, leave and activity events per participant.
Audio ManifestPer-participant audio stream metadata and speech activity windows.
ChatsIn-meeting chat messages.
Audio StreamsPer-participant audio files, when audio_separate_streams is on.
Video StreamsPer-participant video files, when video_separate_streams is on.
ScreenshareScreen share recordings captured during the meeting.
Webhook LogsDelivery history for this bot’s webhook events. Check here first when a webhook “never arrived”.
Raw DataThe API request payload used to create this bot (secrets removed). Confirm your automatic_leave, transcription and platform settings were what you intended.

Triage a bot that did not do what you expected

No Yes NotAllowed Denied Failed Error Stopped Kicked Still there Open the bot Reached In Meeting? Terminal status How did it end? Not admitted in time Host rejected it Meeting blocked the join Setup or service error Read the message Removed by a participant Check automatic leave
  1. Find the terminal status. It is the last in-meeting stage in the timeline, or bot_event on the bot.stopped webhook.
  2. Read the message and ErrorReason. The message is in the timeline and the webhook. ErrorReason (Google Meet and Teams) is in GET /api/v1/bots/{bot_id}/detail. Zoom bots put the cause in the message.
  3. Look it up in the Bot status and error code reference. Every row links to the fix.
  4. Check the request in the Raw Data tab if a setting did not behave as expected, and compare it with what each platform supports.

The lifecycle in webhooks

A successful recording bot sends:

bot.joining → bot.in_waiting_room → bot.inmeeting → bot.recording
→ bot.leaving → bot.stopped
→ audio.processed → transcription.processed → video.processed → bot.done

Key semantics:

  • Exactly one terminal delivery per bot, always with event: "bot.stopped". Its bot_event gives the reason: bot.stopped (normal), bot.kicked, bot.notallowed, bot.denied or bot.failed.
  • A kick and a clean exit both report bot_status: "Stopped", and failure statuses vary in casing (FAILED, ERROR, Failed). Branch on bot_event to tell them apart.
  • Clean terminals use status_code: 200; failure terminals use status_code: 500. Failure message strings are prefixed "Error: ..." (unexpected) or "Failed: ..." (handled: validation, timeout, denial) — useful for alert routing.
  • bot.done always has status_code: 200 and fires on every path, streaming-only and never-admitted bots included. It means the post-call pipeline finished, not that every step succeeded, so check the individual artifact events.
  • On Zoom only, bot.recording_permission_allowed / denied fire between bot.inmeeting and bot.recording — see Zoom bots.

Deliveries carry an event key and, on most events, a bot_event key (live-verified). They match except on terminals: a lobby timeout delivers event: "bot.stopped" with bot_event: "bot.notallowed" and bot_status: "NotAllowed". Branch on bot_event for the specific reason.

Symptom to cause

SymptomWhat actually happenedWhere to go
”The bot never showed up”It is usually in the lobby. On Google Meet with Host management on, regular attendees may not see the admission request.Status shows InWaitingRoom. See Google Meet lobby troubleshooting.
bot_event: "bot.notallowed"Nobody admitted the bot before waiting_room_timeout (default 600 s on Zoom and Google Meet, 1200 s on Teams). On Zoom, it can also mean the host never started the meeting.Lobby reasons. Raise the timeout, or use a signed-in bot for Google Meet or Microsoft Teams.
bot_event: "bot.denied"A host rejected the join request.Nothing to fix in code; it is a host decision.
bot_event: "bot.failed" with a Failed: messageThe meeting blocked the join: invalid link, restricted meeting, not started, ended or sign-in required.Read ErrorReason or failure_reason in the reference.
bot_event: "bot.failed" with an Error: messageCredentials, token, link format, sign-in or a MeetStream-side problem. On Zoom, a non-/j/ link fails with Invalid meeting_link format.Zoom messages, Teams sign-in reasons.
Teams bot fails with TeamsSessionNotApplied although it is not signed inThe organizer’s tenant does not allow anonymous users to join.See the Teams reasons.
Zoom bot ends Stopped with Unable to join external meetingThe meeting belongs to another Zoom account and the bot joined without a token.Use zoom.obf_url or zoom.zak_url. See Zoom authenticated bots.
Bot left “on its own” mid-meetingA timeout fired: everyone_left_timeout, voice_inactivity_timeout (600 s by default, even if you never set it), no_one_joined_timeout or in_call_recording_timeout.The bot.stopped message names the trigger. Tune in automatic leave.
Bot stays in an empty or finished meetingThe timeout you relied on is not enforced on that platform yet (for example no_one_joined_timeout on Zoom).Check the per-platform table; stop the bot with Remove Bot.
Bot left when only another notetaker remainedYour bot_detection.using_participant_names matched.The message contains only other bots remain. See bot detection.
Zoom bot joined but never recordedThe host denied recording or did not answer within recording_permission_denied_timeout (60 s default).bot.recording_permission_denied, then bot.stopped.
Audio or video endpoint returns 202Artifacts are not ready yet: per-participant streams return 202 while the bot is still in the meeting or processing.Poll again in 10 to 30 seconds.
Transcript “not ready” after bot.stoppedPost-call transcription is still running, or you used a live-only provider (deepgram_streaming, assemblyai_streaming) or meeting_captions, which never produce transcription.processed.Retry shortly. For live providers your webhook already has the text; for captions, /detail returns caption_file.
Artifact endpoints return 404 on an old botThe media was deleted by retention or with the delete endpoint. Status is MediaExpired and a data_deletion event fired.See usage and retention.
Webhook never arrivedDelivery is best-effort: non-2xx responses and unreachable URLs are not retried.Check the Webhook Logs tab, then test locally with meetstream listen.

Watch it live

Two fast feedback loops while developing:

# 1. Pretty-print every webhook event as it happens
meetstream listen --port 3333 # then: ngrok http 3333
meetstream bot create "<link>" -c https://<ngrok>.ngrok.io/webhook
# 2. Poll a bot to a terminal state
meetstream bot status <bot_id> --watch

Or let your agent do it: the MeetStream MCP server exposes bot status, details and transcripts as tools, so Claude can debug a bot session for you.

Status reference

Values you will see in GET /api/v1/bots/{bot_id}/status, /detail and webhooks:

Scheduled · Joining · InWaitingRoom · InMeeting · Recording (timeline only) · RecordingPermissionAllowed · RecordingPermissionDenied · Leaving · Stopped · Kicked · NotAllowed · Denied · Failed · Error · MediaProcessing · Done · MediaExpired · Cancelled

What each one means, which are terminal, and how the dashboard labels them: Bot status and error code reference.

Next steps