Skip to main content
The webinar tools on https://api.flywheel.cx/mcp cover everything the dashboard’s Webinars area does: sessions on the calendar, recurring series that build sessions ahead, registrants and attendance, the plan of messages each registrant receives, revenue, and the link to the platform the session runs on (Demio, WebinarJam or Zoom). They are thin wrappers over the same functions the REST endpoints use, so behaviour matches the Webinars API exactly. Read tools need the webinars.read permission; write tools need webinars.write. For the rest of the server see the MCP overview.
register_for_webinar and set_webinar_attendance send real messages to real people. update_webinar with a new start_time or status: "canceled" moves or stops messages people are waiting for. Confirm with the human you are working for before calling any of them on a session that has, or will have, real registrants.

The webinar model: sessions, series and occurrences

Flywheel’s webinar vocabulary, as the webinar tools use it:
  • Session (a “webinar”). One event on the calendar: one org_webinars row with a title, start_time, optional duration_minutes, timezone and join_url, a status, and optionally a link to a platform. Every tool that takes webinar_id takes a session id. The older REST endpoints call the same object a webinar; the id is the same.
  • Series. A repeat rule (a cadence in a timezone) that keeps build_ahead_count future sessions built and open for sign-ups. It builds each one by copying its template session: title, duration, join link, timezone and the session’s own messages. The template is the first session, created with the series.
  • Occurrence. One date on a series’ cadence, written as a local date yyyy-MM-dd in the series timezone. A built session carries it as series_occurrence_date. An occurrence can be built (it has a session), unbuilt (still ahead, not built yet), or skipped.
  • Materialize. The act of building the sessions a series is short of. It runs on its own just after each series session starts, after every series write, and on demand with materialize_webinar_series.
  • The session’s own messages. Broadcasts tied to the session and timed from its start or end (“1 day before start”, “2 hours after it ends”). They go to the session’s registrants at a fixed moment. Create them with the broadcast tools; see Broadcasts over MCP.
  • Bound sequences and workflows. Automations whose trigger is webinar_registration (fires per sign-up) or webinar_attendance (fires after the session ends, per registrant whose outcome matches). They are bound to one session, to every session of a series, or to any webinar. See Workflows over MCP.
  • Person. A contact. person_id is the person UUID from search_people, list_people or identify_person (People over MCP).
Session statuses are draft, scheduled, live, completed and canceled. Series statuses are active, paused and archived. Ids are UUIDs read from list and get tools. Never invent one. The only ids you generate yourself are the optional idempotency keys (webinar_id on create_webinar, new_webinar_id on clone_webinar, series_id on create_webinar_series), which must be fresh v4 UUIDs.

Webinar tools at a glance

Listing and reading sessions: list_webinars and get_webinar

list_webinars pages through sessions. Every argument is optional:
  • status: one of draft, scheduled, live, completed, canceled.
  • series_id: only sessions a series built (the template included).
  • starts_after: sessions starting at or after this ISO-8601 instant. starts_before: sessions starting before it.
  • order: asc or desc by start time. Default desc (latest first).
  • limit: 1 to 200, default 50. offset: default 0. Out-of-range values are refused, not clamped.
For “what is coming up”, pass starts_after as now and order as asc:
It returns { "webinars": [...], "total": 37 }. Each session carries id, title, status, start_time, end_time (start plus duration, null while the duration is unknown), timezone, duration_minutes, join_url, series_id, series_occurrence_date, automation_id, platform and created_at. platform is null when the session is not linked, otherwise { "provider": "zoom", "event_id": "81234567890", "date_id": null }. get_webinar takes webinar_id and returns { "webinar": {...} } with the same fields plus:
  • series: { id, name, status } when the session belongs to a series, else null.
  • registrations: { registered, attended, no_show, unresolved }. no_show counts people whose attendance was resolved and who did not attend; unresolved is registered with attendance not known yet. These are exact database counts.
  • messages: the session’s own broadcasts by status: { total, draft, scheduled, sending, sent, stopped, error }.
get_webinar counts messages but does not show what they say or when they land. Use get_webinar_plan for that.

Creating a one-off session: create_webinar

create_webinar makes one session. For something that repeats, use create_webinar_series instead.
It returns { "webinar": {...}, "created": true }. Calling again with the same webinar_id writes nothing and returns the existing session with created: false, so a retry after a timeout is safe. If that id belongs to another organization’s session, the call fails with “already exists”. A new session has no messages, no registrants and no platform link. Next steps: link it with link_webinar_platform, add its messages with the broadcast tools, make sure a sequence or workflow is bound to it, then check the result with get_webinar_plan.

Editing, rescheduling and status: update_webinar

update_webinar patches one session. Omitted fields stay as they are; null clears timezone, duration_minutes, join_url or automation_id. Accepted fields: title, start_time, timezone, duration_minutes (1 to 1440), join_url, status, automation_id. Unknown fields are refused.
It returns { "webinar": {...}, "schedule_effects": null }, or a schedule_effects object when the start or duration changed (see the next section). A call that changes nothing returns the session untouched. Editing a session never touches the series it belongs to. Editing a series’ template session does change what the series builds from then on.

What rescheduling does to planned messages (update_webinar start_time and duration_minutes)

A new start_time (a reschedule) or a new duration_minutes re-anchors everything timed off the session, and the response reports what moved:
  • waits_recomputed: pending waits in running workflow and sequence runs that are anchored to the session start (“wait until 1 hour before start”) moved onto the new start. Only a start change moves these; a duration change alone reports 0.
  • messages_rescheduled: the session’s own messages that are timed from its start or end were re-scheduled. A duration change moves the “after it ends” ones even when the start stays put.
  • attendance_sync_rearmed: the linked platform’s post-session attendance pull was moved to the new end time. false when the session is not linked.
Steps timed relative to each person’s own registration (“2 days after they register”) are not anchored to the session and do not move. A reschedule tells nobody by itself. If registrants should hear about the new time, that is a message you plan and schedule separately, after the human agrees to the wording.
Rescheduling one session of a series moves only that session. The series’ cadence is unchanged, and the session keeps its series_occurrence_date.

Session status moves and cancelling (update_webinar status)

status moves are validated. Anything else is refused with the moves that are allowed: A scheduled session cannot go back to draft (people may already be registered); cancel it instead.
What cancelling does, as the code enforces it:
  • The session’s own messages do not send. Each one is checked against the session status at send time and skipped while the session is canceled.
  • register_for_webinar and set_webinar_attendance refuse the session, because both would start registration and follow-up messages for a session that is not happening.
  • A canceled series session keeps its date: the series will not rebuild that date, and builds a replacement session further out to keep its build-ahead count.
What cancelling does not do: nothing in the cancel path stops runs of sequences or workflows that registrants were already enrolled into. Read get_webinar_plan before cancelling so you and the human know what those runs still hold. Restoring (canceled → scheduled) re-enables the session’s messages. Messages whose moment passed while it was canceled are not re-sent.

Deleting a session: delete_webinar

delete_webinar removes a session only when it never gathered anything. It is refused, with the reason, when the session:
  • belongs to a series (the series would rebuild the date) or is a series’ template;
  • has any registrations;
  • has any message that is sending or sent.
In each of those cases cancel instead (update_webinar with status: "canceled"), which keeps who registered and what went out. For a series date that has not been built yet, use skip_webinar_occurrence.
Returns { "deleted": true }. A successful delete also cancels the session’s pending attendance pull and removes its unsent messages with it.

Copying a session: clone_webinar

clone_webinar copies a session to a new start time. The copy gets the source’s title (or title if given), timezone, duration, join link, automation_id, and all of its messages, whatever state they reached on the source.
  • Every copied message is a draft. Nothing a clone copies sends until someone schedules it.
  • Messages timed from the session are re-anchored to the new start or end with the same offset. A message that was scheduled at a fixed time (no offset) is copied with no send time, for the human to date.
  • Registrations are not copied; the clone starts empty.
  • The platform link is not copied (two sessions on one platform id would share every registration and attendance webhook). Link the clone with link_webinar_platform.
  • The clone is not part of any series, even when the source is.
Returns { "webinar": {...}, "created": true, "messages_cloned": 4 }. A retry with the same new_webinar_id returns the clone already made with created: false and messages_cloned: 0. To make the copied messages send, schedule each one with the broadcast tools after the human has reviewed them.

Creating a recurring webinar: create_webinar_series

create_webinar_series creates a series and its first session in one call. The first session lands on the first cadence slot after now, becomes the series’ template, and is bound into the series as its own occurrence. The series then builds sessions until build_ahead_count future sessions exist. A weekly Thursday 7pm webinar in New York, four sessions ahead:
Returns { "series": {...}, "template_webinar": {...}, "created": true, "sessions_built": 3 }. sessions_built counts sessions built beyond the template. A retry with the same series_id returns the existing series with created: false and runs the build again (which only fills gaps). Sessions built at creation copy an empty template. Each later session copies the template as it is at the moment it is built, so set the template up straight away:
  1. Add the template session’s messages (broadcast tools, webinar_id = template_webinar.id) and schedule them. Messages that are scheduled or already sent on the template are copied armed: they are scheduled on each new session automatically. Draft and stopped messages are copied as drafts. A message scheduled at a fixed time (no offset from the session) is copied as a draft with no time.
  2. Link the template to its platform (link_webinar_platform).
  3. Bind a sequence or workflow to the series (trigger with series_id) so every session’s registrants enroll.
  4. Fill the sessions that were built before the template was ready: they are separate copies and do not pick up later template edits to messages. Set each one up, or check each with get_webinar_plan.
Each built session copies the template’s title, timezone, duration, join link, platform name and automation_id, with status scheduled. The platform link is carried onto built sessions only where one link can serve every date:

Reading series: list_webinar_series and get_webinar_series

list_webinar_series returns { "series": [...] }, newest first, optionally filtered by status (active, paused, archived). Each series carries id, name, status, timezone, cadence (interval_unit, interval_count, weekday, time_of_day as HH:mm:ss), cadence_summary (for example "Every Thursday · 7:00pm"), build_ahead_count, template_webinar_id and created_at.
get_webinar_series takes series_id and returns { "series": {...} } with the same fields plus:
  • sessions: the sessions the series has built, template included, newest start first, at most 100.
  • skips: [{ "occurrence_date": "2026-11-26", "reason": "Thanksgiving" }].
  • upcoming_unbuilt: the next six dates the series has not built yet, each { "occurrence_date": "2026-11-12", "start_time": "2026-11-13T00:00:00.000Z" }. These are the dates skip_webinar_occurrence accepts.

Changing a series: update_webinar_series

update_webinar_series patches the repeat rule: name, cadence (any subset of its fields), timezone, build_ahead_count (0 to 52) and status.
  • The rule applies to sessions not built yet. Sessions that already exist keep their times; move one with update_webinar (a reschedule).
  • Switching interval_unit to day drops the weekday. Switching to week needs a weekday unless the series already has one.
  • status: "paused" or "archived" stops the series building and clears its pending check. "active" resumes it.
  • What each new session copies comes from the template session, not from the series. Edit the template to change future sessions; update_webinar_series has no title, duration or message fields.
Every call ends by running the build immediately and returns { "series": {...}, "materialized": {...} } (see the next section for the materialized shape). Raising build_ahead_count therefore builds the extra sessions in the same call.

Occurrences: skip_webinar_occurrence, unskip_webinar_occurrence and materialize_webinar_series

skip_webinar_occurrence tells a series not to build one upcoming date. Take the date from get_webinar_series upcoming_unbuilt.
Returns { "series_id": "...", "occurrence_date": "2026-11-26", "created": true }; created: false when the date was already skipped. It is refused when:
  • occurrence_date is not yyyy-MM-dd;
  • a session is already built for that date. The error names the session; cancel it with update_webinar (status: "canceled") instead, which also holds the date;
  • the date is not one the cadence would build (wrong weekday, in the past, or more than 104 slots out). The error lists the next unbuilt dates.
unskip_webinar_occurrence puts a skipped date back and immediately runs the build, so a date inside the build-ahead horizon gets its session at once. It returns { "series_id", "occurrence_date", "materialized": {...} } and is refused with not-found when the date is not skipped.
materialize_webinar_series builds any sessions the series is short of right now, instead of waiting for its next check (which runs about five minutes after the next session starts, or hourly when it has none ahead). It only fills gaps, so it is safe to repeat.
It returns:
created is sessions built on this run. skipped is dates another concurrent run had already built. next_run_at is null when the series is paused or archived; such a series builds nothing. How the build counts: it keeps build_ahead_count future sessions that are not canceled. A date is taken when it has a session (in any status, canceled included) or is skipped. So cancelling a future series session makes the series build one more date at the end of its horizon, and skipping a date makes it step over that date.

Registrants: list_webinar_registrants

list_webinar_registrants lists who registered for a session and what the session knows about each person.
The response:
  • provider: the platform the session runs on. Watch depth exists only where the platform reports it.
  • segment_counts: the size of every segment across all registrants, whatever segment and paging you asked for.
  • registrants: each with person_id, name, email, registered_at, attended, attendance_resolved, watched_minutes, watch_pct, left_early, clicked_offer_count, purchase_amount, purchase_attributed, refunded, and segments (every segment the person is in).
  • total: registrants matching the filter across all pages, plus the limit and offset used.
attended: false with attendance_resolved: false means not known yet, not a no-show. The no_show segment only holds resolved non-attendees.

Registering someone: register_for_webinar

register_for_webinar sends real messages. It enrolls the person into every live sequence and workflow bound to the session (confirmations, reminders) and pushes them to the linked platform. Get the human’s go-ahead before registering real people, and say which session and which person.
It takes a session and an existing person. Resolve the person first: search_people for someone already in Flywheel, or identify_person to find-or-create from an email or phone (People over MCP).
Returns:
join_url is the link this person joins with, the same one their messages carry. It is their own link when the linked platform issued one (join_url_personal: true), otherwise the session’s shared join_url (join_url_personal: false), and null when the session has neither. Only hand someone a link as “your join link” when join_url_personal is true: on a Zoom webinar that requires registration, the shared link opens Zoom’s registration form, not the room. What one call does, in order:
  1. Creates the registration (one per person and session).
  2. Pushes the registrant to the linked platform, so their personal join link exists before any reminder uses it: Demio and WebinarJam when linked, and Zoom when linked. A platform failure never fails the registration; it is logged. Zoom pushes are never retried, because Zoom allows only three adds per person, webinar and day.
  3. Enrolls the person into every live sequence and workflow whose webinar_registration trigger matches this session, and records a $fw_webinar_registration event on their timeline.
Registering someone twice is safe: the second call writes nothing, sends nothing and returns already_registered: true with the same join_url. A canceled session refuses registrations. A platform push that fails or is skipped still returns a registration; its join_url is the session’s shared link with join_url_personal: false. To see exactly what a registration made now will send, call get_webinar_plan with entry_times set to now before registering.

Recording attendance: set_webinar_attendance

set_webinar_attendance arms the session’s outcome follow-ups (the attended and no-show sequences), so it sends real messages after the session ends. Confirm with the human first.
It records whether a registered person attended. attended: false marks a no-show.
Returns { "person_id": "...", "attended": true, "attendance_resolved_at": "2026-10-21T00:12:09.000Z" }.
  • The person must already be registered; otherwise it fails with “register them first”.
  • A canceled session refuses attendance.
  • Marking someone attended again keeps the first time they were marked present.
  • Marking by hand reports no watch depth, so watch_pct and left_early stay unknown for that person.
  • Outcome sequences enroll through a sweep that never runs before the session’s end, even if you mark attendance during the live session.
Linked platforms report attendance on their own after the session ends (Demio, WebinarJam and Zoom). Use this tool for sessions without a platform link, or to correct a record.

Sequences and workflows bound to a session

A session’s messages come from two places, and get_webinar_plan shows both:
  1. The session’s own messages: broadcasts with audience: "webinar" and this webinar_id, timed with webinar_offset_minutes from webinar_anchor start or end (positive is before the anchor, negative after). They go to the session’s registered, attended or no_show audience at one fixed moment. Manage them with the broadcast tools (Broadcasts over MCP).
  2. Bound sequences and workflows: automations with one of two triggers:
    • webinar_registration: fires once per new registration. Steps count from the moment the person registered, or from the session start for anchored waits.
    • webinar_attendance: fires once per registrant after the session has ended, for those whose outcome matches: attended, no_show, left_early, watched_min_pct (with min_watch_pct), clicked_offer, purchased or refunded. Steps count from the session end.
Each trigger’s params bind it with webinar_id (one session), series_id (every session of a series) or neither (any webinar in the organization). A trigger carrying both only fires for that session inside that series. Only a live, published automation enrolls anyone; a draft version enrolls nobody.
The automation_id on a session is a label only. Setting it with create_webinar or update_webinar binds nothing and enrolls nobody. To build or change a bound workflow over MCP, follow Workflows over MCP: get_node_types for the trigger shapes, get_vocabulary for valid webinar ids, then create, publish and set it live. set_workflow_status live starts real sends, so confirm with the human first. A series binding is the right choice for a recurring webinar: every session the series builds is covered without touching the workflow again.

Reading the send plan: get_webinar_plan

get_webinar_plan shows everything a session sends and when, and resolves it into the exact, timestamped list someone receives if they register at a given moment. Call it before and after any change to a session’s schedule, messages or bound sequences, and show the human the result before anything goes live.
entry_times is up to 12 ISO-8601 registration moments. Leave it out and four are sampled: ten days before the start, three days before, two hours before, and one hour after the end. The response is { "spec": {...}, "previews": [...] }.

The plan spec (get_webinar_plan spec)

spec is the plan before it is resolved for anyone:
  • webinar_id, title, status, start_time, end_time (start plus duration, or the start when there is no duration), duration_minutes.
  • broadcasts: the session’s own messages, each with broadcast_id, channel (email or sms), label (subject or the start of the text), status, anchor (start or end), offset_minutes (signed, positive is before the anchor, null when not timed from the session) and scheduled_at.
  • sequences: one entry per bound sequence, live and paused, with automation_id, name, status, role (registration or the outcome it fires on), binding (webinar, series or any), stops_at_live (it carries the “webinar has started” exit) and its steps.
  • workflows: bound workflows that are live, with automation_id, name, binding, step_count and stops_at_live.
  • workflow_steps: the flattened steps of everything live that enrolls people, each with action_id, automation_id, channel, label, timing_type (immediate, relative or anchored), relative_seconds, anchor_offset_seconds, branch, skippable and counts_from (entry or session_end).
Paused sequences appear in spec.sequences so you can see them, but their steps never reach workflow_steps or the previews: a paused sequence sends nothing.

Resolved previews (get_webinar_plan previews)

Each entry in previews resolves the plan for one registration moment:
  • entry_time and entry_label (for example "2 hours out" or "after it ended").
  • entries: every message in time order, each with kind (broadcast, workflow_step or live_wall), lane (clock for fixed-moment items, registration for steps counted from sign-up, outcome for post-session steps), channel, label, at, skipped, skip_reason and skip_sentence. The live_wall entry marks the session start.
  • density: total, delivered, email, sms, other, skipped and the delivered span. density_sentence says it in words, for example "6 messages over 3 days · 5 email, 1 text".
  • collisions: pairs of delivered messages landing within 15 minutes of each other for this registrant, each with a sentence to show the human.
Skip reasons:
  • moment_passed: a step whose moment was already gone when they registered. The runtime skips it rather than sending it late.
  • stopped_at_live: a registration-timed step landing after the start, in an automation that stops when the webinar starts.
  • before_registration: a session message that went out before they registered. They were not in its audience.
Reading it well:
  • Compare the early and late entry times. A ladder of reminders that looks fine ten days out can stack three messages into one hour for someone who registers on the day; collisions names exactly that.
  • Check spec.broadcasts[].status. Previews place every session message by its timing, drafts included; a draft or stopped message only sends once it is scheduled.
  • An entry with at: null is a session message with no send time yet. It sorts last.
  • An empty plan (density_sentence "Nothing lands for this registrant.") on a session with registrants usually means no live sequence is bound to it.

Revenue: get_webinar_revenue and get_webinar_series_revenue

get_webinar_revenue reports what one session earned:
It returns registered_count, attended_count, click_count, unique_clicker_count, buyer_count, and revenue blocks. Each block has purchase_count, gross_revenue, refunded_revenue, net_revenue and new_net_revenue (net excluding renewals). The blocks:
  • total: everything in the session’s scope. It splits into three disjoint parts:
  • broadcast_attributed: attributed to one of this session’s own messages.
  • message_attributed: attributed to another send, such as a workflow step.
  • unattributed: real revenue with no attributing click.
  • attributed: broadcast_attributed plus message_attributed.
broadcasts breaks it down per session message: broadcast_id, subject, status, scheduled_at, sent_count, click_count, unique_clicker_count, buyer_count and its attributed block. get_webinar_series_revenue reports a series:
It returns the same counts and blocks computed over the distinct purchases across the series (one buyer registered for two sessions is one sale), session_count, and sessions: a per-session rollup of the most recent session_limit sessions (1 to 100, default 50). The per-session figures do not add up to the series total by design. Report the series figures as the series result and the sessions as a breakdown.
Both tools return reconciles and reconciliation_issues. When reconciles is false, the figures do not add up: tell the human that, rather than reporting the numbers.
link_webinar_platform connects a session to the platform it runs on. From then on, registrations are pushed there (so each person gets their personal join link) and attendance comes back after the session ends. The platform must be connected for the organization under Settings → Integrations; Demio and Zoom refuse the link otherwise. It returns { "webinar": {...}, "topic": "...", "recurring": null }. topic is the platform’s own name for what was linked (Demio and Zoom), so you can confirm with the human it is the right one. recurring is set for Zoom only. Linking to a different platform than before cancels the old platform’s pending attendance pull. Re-linking replaces the stored platform snapshot, so a link to a different event never reuses the old one’s details. event_id is the Demio event; date_id is the session inside it. Leave date_id out to use the event’s own timestamp.
By default (sync_details true) the session’s title, start time (the chosen date’s, or the event’s), duration, timezone and join link (Demio’s registration URL) are overwritten from Demio. When that moves the start or duration, everything timed off the session is re-anchored exactly as a reschedule does. Pass "sync_details": false to keep the session’s own details. The event id must load from the connected Demio account, or the link is refused. event_id is the WebinarJam webinar id; date_id is its schedule id, which says which of the webinar’s dates this session is.
  • Leave date_id out on a webinar with several schedules and the schedule running at this session’s start (within 60 minutes) is picked, when exactly one matches and the WebinarJam API key is connected. No match, or no API access, leaves it blank. On a single-schedule webinar the link is webinar-level.
  • Two sessions cannot be linked to the same WebinarJam webinar and schedule; the second link is refused.
  • WebinarJam reports to Flywheel through custom webhooks, routed by the ids this link records. The human must also add three automation rules on the WebinarJam side (registration, attended live, missed live); linking does not create them.
  • Without a connected WebinarJam API key the link is still recorded, but registrations cannot be pushed to WebinarJam.
WebinarJam recurring schedules do not work with Flywheel series. A recurring WebinarJam webinar keeps one schedule id for every date, so Flywheel cannot tell its dates apart: every registration WebinarJam sends routes to the one session holding that link, and once that session has ended, registrations arriving for later dates are refused rather than landed on the wrong session. The supported setup is one WebinarJam schedule per date. With that, a series whose template is linked to the WebinarJam webinar links each future session it builds to the schedule on that session’s date, on every build run (this needs the WebinarJam API key connected). A date with no matching schedule (or one already taken) stays unlinked until a schedule is added in WebinarJam; the next run picks it up.
event_id is the Zoom webinar id as Zoom shows it. Spaces and dashes are fine ("812 3456 7890" or "81234567890"); it must be 9 to 12 digits.
  • Zoom must be connected, and the webinar must exist on the connected Zoom account. A meeting id, a typo or a webinar on another account is refused with “No webinar … on the connected Zoom account”.
  • A one-off Zoom webinar can back only one session. A recurring Zoom webinar (recurring: true in the response) can back several sessions. Attendance and the “webinar ended” event go to the session whose scheduled time the running falls in, or nearest to within 12 hours; a running that far from every session is not recorded.
  • A series on a recurring Zoom webinar needs one link. Link the series’ template session and every upcoming session of the series is linked to the same Zoom webinar: the ones already built at that moment, and each one the series builds afterwards. Creating the series first and linking the template after works the same as the other order. Moving the template to another recurring Zoom webinar moves those sessions with it. A session of the series left unlinked is linked again on the next build run, so to take one date off the webinar, link that session elsewhere or cancel it. A session you linked to a different webinar by hand is left alone.
  • For registrants to get a personal link on a recurring webinar, set it up in Zoom as: registration required, and “attendees register once and can attend any of the occurrences”. Someone registered for more than one date keeps one Zoom registration and one link. A recurring webinar that registers people per date is not pushed to.
  • Errors say what to fix: reconnect Zoom, the app needs installing by an account admin, or Zoom did not answer (try again).
  • The attendance pull is armed for the session’s end; Zoom’s own “webinar ended” event brings it forward.
unlink_webinar_platform disconnects a session from Demio, WebinarJam or Zoom. New registrations stop being pushed to the platform, inbound platform events stop routing to the session, and the pending attendance pull is cancelled. Registrations and attendance already recorded stay.
Returns { "webinar": {...} } with platform: null. Unlinking a series’ template session from Zoom also unlinks the series’ upcoming sessions that shared that Zoom webinar. Sessions that have ended keep their link and their attendance.

Common webinar tasks

What webinars are coming up, and how are they filling?

  1. list_webinars with starts_after = now, status scheduled, order asc.
  2. get_webinar on each session the human cares about for registrations and messages counts.

Set up a one-off webinar end to end

1

Create the session

create_webinar with title, start_time (with offset), timezone, duration_minutes and a fresh webinar_id.
2

Link the platform

link_webinar_platform. Read topic back to the human to confirm it is the right event.
3

Add the session's messages

create_broadcast with audience: "webinar", the webinar_id, and webinar_offset_minutes / webinar_anchor, then plan and schedule with the human’s go-ahead. See Broadcasts over MCP.
4

Bind a registration sequence

A live workflow with a webinar_registration trigger bound to this webinar_id (or reuse one bound to any webinar). See Workflows over MCP.
5

Check the plan

get_webinar_plan with no entry_times. Show the human the density_sentence and any collisions for each sample.

Set up a weekly webinar

  1. create_webinar_series with the cadence, timezone, duration and a fresh series_id.
  2. Set up template_webinar from the response: messages (scheduled, timed from the session), platform link. A recurring Zoom webinar linked here is linked on every upcoming session of the series too.
  3. Bind sequences with series_id on the trigger, so every built session’s registrants enroll.
  4. get_webinar_series and get_webinar_plan on each session already built; those were copied before the template was ready.

Move a session to a new time

  1. get_webinar_plan on the session and get_webinar for its registration count.
  2. Tell the human what will move, and get their go-ahead.
  3. update_webinar with the new start_time. Report schedule_effects back.
  4. If the session is linked to Demio, WebinarJam or Zoom, the time must also change on the platform; Flywheel does not change it there.
  5. get_webinar_plan again to confirm the new timings.

Skip a holiday on a series

  1. get_webinar_series and find the date.
  2. If it is in upcoming_unbuilt: skip_webinar_occurrence with that occurrence_date and a reason.
  3. If a session is already built for it: confirm with the human, then update_webinar that session with status: "canceled".

Register someone the human named

  1. search_people (or identify_person with their email) to get person_id.
  2. get_webinar_plan with entry_times = now, so the human sees what this person will receive.
  3. With their go-ahead, register_for_webinar. Report already_registered.

Follow up after a session

  1. list_webinar_registrants with segment attended, no_show, clicked_offer or purchased.
  2. get_webinar_revenue; check reconciles before quoting numbers.
  3. Corrections to attendance: set_webinar_attendance, with the human’s go-ahead.

Webinar mistakes to avoid

  • Registering or marking attendance without asking. register_for_webinar and set_webinar_attendance send real messages. Confirm first, every time, and name the session and person.
  • Using automation_id to attach a sequence. It is a label. Enrollment comes only from live automations with a webinar_registration or webinar_attendance trigger.
  • Deleting what should be cancelled. delete_webinar refuses anything with registrations, sent messages or a series. Cancel instead; it keeps the history.
  • Assuming cancel stops everything. Cancel stops the session’s own messages and refuses new registrations and attendance. It does not stop sequence runs already in progress.
  • Rescheduling and assuming the platform follows. update_webinar moves Flywheel’s clock, not Demio’s, WebinarJam’s or Zoom’s. Change the time there too.
  • Expecting a series edit to move built sessions. update_webinar_series only shapes sessions not built yet. Reschedule existing ones with update_webinar.
  • Editing the series instead of the template. Title, duration, join link and messages of future sessions come from the template session. Edit that session.
  • Leaving template messages as drafts. A series copies draft messages as drafts on every session, so they never send. Schedule them on the template.
  • Skipping a built date. skip_webinar_occurrence refuses a date that already has a session. Cancel that session instead.
  • Cloning and expecting it to send. clone_webinar copies messages as drafts and drops the platform link. Schedule the messages and link the clone.
  • Linking a recurring WebinarJam webinar to a series. One schedule id covers every date, so Flywheel cannot tell them apart. Use one WebinarJam schedule per date.
  • Linking a Zoom meeting id. Only a Zoom webinar on the connected account links.
  • Reading a preview as “this sends”. Previews include draft session messages. Check spec.broadcasts[].status.
  • Quoting revenue that does not reconcile. When reconciles is false, say the numbers do not add up. And never sum sessions to get a series total.
  • Inventing ids. Read ids from list and get tools; only idempotency keys are generated, as fresh v4 UUIDs.