Tutorial10 min read

Meeting No-Show Detection: How to Know When Nobody Turned Up (and What to Do About It)

Missed meetings cost teams money and erode trust. The Gregnote API now fires a bot.no_show webhook event the moment it detects a no-show — so your product can react instantly instead of leaving users waiting in the dark.

Gregnote Team

26 August 2026

The problem nobody talks about

You've built a meeting bot integration. It joins the call, records the audio, fires a meeting.completed webhook with the full transcript. Smooth.

But what happens when nobody shows up?

The bot joins. The host's audio starts. Five minutes pass. Ten minutes pass. The invited guest never appears. The host gives up and leaves. The meeting ends — and your system fires a meeting.completed event with a transcript containing one person talking to nobody, or an empty transcript with no useful data at all.

Your product just billed for a bot session. Your user got no value. And unless you've built explicit handling for this scenario, your CRM, your Slack notification, or your meeting notes tool has just produced a ghost entry: a record of a meeting that didn't really happen.

This is one of the most common failure modes in meeting intelligence products — and it's one that Gregnote now detects automatically.

Two kinds of no-show

There are actually two distinct no-show scenarios, and they have different causes and different appropriate responses:

1. Owner no-show (bot.owner_no_show)

The meeting was scheduled. Your bot was invited and joined the waiting room. But the meeting host — the person who created the meeting — never started it. The bot waited, timed out, and left.

This is an owner no-show. The meeting room was never opened. Nobody was in it. Common causes: the host double-booked, forgot, or rescheduled without updating the meeting link.

Gregnote detects this via a configurable waiting room timeout. When the bot sits in the waiting room longer than the threshold (default 10 minutes, configurable up to 60 minutes on Pro), it fires bot.owner_no_show and withdraws.

2. Invitee no-show (bot.no_show)

The meeting host showed up. The bot was admitted. The host spoke — maybe presented, maybe just waited. But the invited guest never joined. Eventually the host left, the meeting ended, and Vexa reported left_alone as the completion reason.

This is an invitee no-show. The meeting opened but the other party didn't come.

Detecting this correctly is more subtle. A left_alone completion reason alone isn't enough — a real two-person meeting where the second person leaves slightly before the first also produces left_alone. The correct signal is the speaker count: if the transcript contains only one human speaker (the owner), the invitee was never meaningfully present. Gregnote checks the speaker list after the meeting ends, and if at most one non-bot speaker appears, it fires bot.no_show instead of meeting.completed.

The webhook events

Both no-show scenarios fire distinct webhook events with clean payloads.

bot.owner_no_show

Fired when the bot timed out in the waiting room before the meeting started:

{
  "event": "bot.owner_no_show",
  "bot_id": "bot_01J8ABCD...",
  "data": {
    "bot": {
      "id": "bot_01J8ABCD...",
      "platform": "google_meet",
      "meeting_url": "https://meet.google.com/abc-def-ghi"
    },
    "metadata": { "crm_deal_id": "deal_123", "user_id": "user_456" }
  }
}

bot.no_show

Fired when the meeting ended with only one human speaker (owner only — invitee never joined):

{
  "event": "bot.no_show",
  "bot_id": "bot_01J8ABCD...",
  "data": {
    "bot": {
      "id": "bot_01J8ABCD...",
      "platform": "google_meet",
      "meeting_url": "https://meet.google.com/abc-def-ghi"
    },
    "no_show_type": "invitee",
    "metadata": { "crm_deal_id": "deal_123", "user_id": "user_456" }
  }
}

Note: bot.no_show is a Pro-only feature. Free plan bots that end with no invitee will still receive meeting.completed with an empty or single-speaker transcript.

How to handle it in your webhook handler

Your webhook handler should branch on the event type before doing anything else:

app.post("/hooks/gregnote", async (req, res) => {
  verifySignature(req, process.env.GREGNOTE_WEBHOOK_SECRET);
  res.sendStatus(200); // acknowledge first, always

const { event, data } = req.body;

switch (event) { case "meeting.completed": await queue.add("process-transcript", data); break;

case "bot.no_show": await queue.add("handle-no-show", { type: "invitee", botId: data.bot.id, meetingUrl: data.bot.meeting_url, metadata: data.metadata, }); break;

case "bot.owner_no_show": await queue.add("handle-no-show", { type: "owner", botId: data.bot.id, meetingUrl: data.bot.meeting_url, metadata: data.metadata, }); break; } }); ```

Then in your handle-no-show job:

async function handleNoShow({ type, botId, meetingUrl, metadata }) {
  if (type === "invitee") {
    // The host showed up but the guest didn't
    await sendNoShowFollowUpEmail(metadata.user_id, metadata.guest_email);
    await logToSlack(`No-show: ${metadata.guest_name} didn't join the call`);
    await updateCrmDeal(metadata.crm_deal_id, { outcome: "no_show", nextStep: "reschedule" });

} else if (type === "owner") { // The meeting was never started — host no-show await notifyHost(metadata.user_id, "Your meeting bot waited but the meeting room was never opened."); await logToSlack(Bot returned: meeting room was never started); } } ```

Real use cases where this changes everything

CRM and sales workflows

No-show detection is arguably most valuable in sales automation. Without it, your CRM gets a completed call record for a meeting that didn't happen — polluting your pipeline with ghost activity.

With bot.no_show, you can: - Mark the deal as "follow-up needed — invitee no-show" - Automatically draft a reschedule email from the rep - Update the deal stage back (a no-show doesn't advance the sale) - Alert the rep immediately so they can follow up while the context is fresh

// HubSpot example
async function handleSalesNoShow(dealId, guestEmail) {
  // Revert deal stage
  await hubspot.crm.deals.basicApi.update(dealId, {
    properties: { dealstage: "appointment_scheduled" } // back to scheduled
  });

// Log the no-show as an activity await hubspot.crm.objects.notes.basicApi.create({ properties: { hs_note_body: "Meeting no-show: invitee did not join. Follow up required.", hs_timestamp: Date.now().toString(), }, associations: [{ to: { id: dealId }, types: [{ category: "HUBSPOT_DEFINED", typeId: 214 }] }] });

// Create follow-up task await hubspot.crm.objects.tasks.basicApi.create({ properties: { hs_task_subject: Follow up — ${guestEmail} no-show, hs_task_priority: "HIGH", hs_task_status: "NOT_STARTED", hs_due_date: tomorrow(), }, associations: [{ to: { id: dealId }, types: [{ category: "HUBSPOT_DEFINED", typeId: 216 }] }] }); } ```

Interview and recruiting platforms

Candidate no-shows are one of the most time-consuming admin problems in recruiting. When a candidate doesn't show for a technical interview, the recruiter needs to know immediately, log the outcome, and decide on next steps.

With bot.no_show: - Automatically close the interview slot as "candidate no-show" - Trigger a templated email asking if the candidate wants to reschedule - Alert the interviewer so they can use the freed hour productively - Track no-show rate per candidate source (useful for quality scoring sourcing channels)

Customer success platforms

For CSM-run calls (QBRs, onboarding sessions, health checks), a no-show from the customer is a signal worth acting on immediately. A customer who didn't show for their QBR is more likely to churn than one who attended.

With bot.no_show: - Flag the account as "at risk" if they missed a scheduled touchpoint - Escalate to the account manager - Automatically reschedule or send a check-in

Education and tutoring platforms

If a student doesn't show for a tutoring session, the tutor's time was wasted. With no-show detection: - Charge the student's account per your cancellation policy (with a clear audit trail) - Don't charge the tutor for a session they couldn't deliver - Notify the student and offer rescheduling

Configuring the waiting room timeout

The owner_no_show event fires after the waiting room timeout elapses. You can configure this per-customer via the settings API:

PATCH /v1/settings
Authorization: Bearer gk_live_YOUR_KEY
Content-Type: application/json

{ "waiting_room_timeout_minutes": 15 } ```

Valid values: 2–60 minutes. Default is 10 minutes. This is a Pro feature.

A shorter timeout (5 min) is appropriate for high-volume sales workflows where you want fast follow-up. A longer timeout (20–30 min) is appropriate for less time-sensitive use cases like async customer check-ins.

Why this matters for billing

Without no-show detection, your platform charges for bot sessions that produced no value. With it, you have the data you need to implement smart billing:

  • Don't bill for owner no-shows: The meeting room was never opened. The bot waited. Consider this a platform cost, not a user cost — charging for it feels unfair and erodes trust.
  • Make a policy decision on invitee no-shows: The bot joined, the meeting opened, resources were consumed. Whether you bill depends on your product philosophy. If you bill, make the no-show event visible in your billing breakdown so users understand what they're paying for.

The is_no_show: true field on the meeting record (returned in the GET /v1/meetings/:id response) makes it easy to filter these out in your billing pipeline.

// Filter no-shows from your billable meetings query
const billableMeetings = meetings.filter(m => !m.is_no_show);
const billedMinutes = billableMeetings.reduce((sum, m) => sum + m.duration_seconds / 60, 0);

The technical detection logic

For engineers curious about how invitee no-show detection works under the hood:

Vexa (the underlying bot provider) fires a completion_reason: left_alone event when the bot is the last entity in the meeting room. This is the primary signal that something unusual happened — a normal meeting ends with participants leaving before the bot, not after.

But left_alone alone isn't sufficient. A real meeting where the second person leaves 30 seconds before the host also produces left_alone. The distinguishing factor is the transcript's speaker list.

Gregnote fetches the transcript after the meeting ends and counts the non-bot speakers. If only one human speaker (or zero) appears, the invitee was never meaningfully present in the audio — they may have joined briefly or not at all. If two or more human speakers appear, a real conversation happened and the event fires as meeting.completed regardless of completion_reason.

This speaker-count approach is robust to the edge cases that trip up simpler implementations. It correctly handles: - Host talks alone for 5 minutes then leaves (no-show ✓) - Guest joins 2 minutes late but stays for 45 minutes (not a no-show ✓) - Guest joins, says nothing, leaves after 30 seconds (edge case — technically present, but Gregnote counts a non-bot speaker appearing in the transcript as "joined")

Availability

bot.no_show (invitee no-show detection) is available on the Pro plan.

bot.owner_no_show (waiting room timeout) fires on both Free and Pro, but the configurable timeout (waiting_room_timeout_minutes) is a Pro setting. Free accounts use the default 10-minute timeout.

The is_no_show field on meeting records is returned on all plans.

See also

For the full webhook handling pattern including signature verification, idempotency, and retry semantics, see webhook reliability patterns. For how to build a complete CRM automation pipeline on top of Gregnote, see using meeting bots to automate your CRM. For the broader architecture of meeting bots in production, see meeting bot architecture at scale.

Try it yourself

API key in 30 seconds. Free credit on sign-up. No card required.