
Cometchat Calls
- 333 installs
- 70 repo stars
- Updated June 23, 2026
- cometchat/cometchat-skills
cometchat-calls is a Claude Code skill that helps developers integrate CometChat Calls for broadcast-style town halls, webinars, and large-audience sessions with presenter-led video and raise-hand Q&A.
About
cometchat-calls is an integration skill for embedding CometChat Calls into applications that need broadcast-style video events. It covers presenter-led sessions where most attendees keep audio and video off, a small presenter set stays visible, and Q&A flows through raise-hand mechanics. The readme documents an end-to-end flow from creating a Broadcast group keyed to an event ID through presenter setup and audience participation. CometChat Calls SDK supports up to roughly 50 simultaneous video streams per call depending on plan; for 100+ attendee broadcasts the skill points toward RTMP streaming bridges to YouTube or Twitch so viewers consume via streaming instead of bidirectional WebRTC.
- End-to-end broadcast flow: presenter group, auto-join calls, default mute for attendees, raise-hand Q&A
- Recommended call settings (SIDEBAR layout, lock layout, moderator roles) for webinar-style UX
- Scale guidance: ~50 simultaneous WebRTC streams; RTMP push to YouTube/Twitch/CDN for 100+ viewers
- Post-event recording upload and replay notification pattern for attendees
- Server-side group pre-creation and password-gated joins for paid or private events
Cometchat Calls by the numbers
- 333 all-time installs (skills.sh)
- Ranked #1,252 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cometchat/cometchat-skills --skill cometchat-callsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 333 |
|---|---|
| repo stars | ★ 70 |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 23, 2026 |
| Repository | cometchat/cometchat-skills ↗ |
How do you add webinar video calls with CometChat?
Wire CometChat Calls into a developer’s app for broadcast-style town halls, webinars, and large-audience sessions with presenter-led video and raise-hand Q&A.
Who is it for?
Developers adding CometChat-powered town halls, all-hands, or webinar sessions who need presenter-led video with raise-hand Q&A at scale.
Skip if: Apps needing custom WebRTC without CometChat or events beyond ~50 simultaneous video streams without an RTMP streaming fallback.
When should I use this skill?
A developer asks to implement CometChat Calls for broadcasts, webinars, town halls, raise-hand Q&A, or RTMP streaming bridges.
What you get
CometChat Broadcast group setup, presenter session flow, raise-hand Q&A, and optional RTMP streaming bridge configuration.
- Broadcast group integration
- Presenter and audience session flow
- Optional RTMP bridge configuration
By the numbers
- Supports up to ~50 simultaneous video streams per CometChat Calls call
- Recommends RTMP streaming bridges for 100+ broadcast audiences
Files
Ground truth: the per-platform Calls SDK +docs/calls. Official docs: https://www.cometchat.com/docs/calls/javascript/overview · Docs MCP:claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp(or fetch the URL directly without MCP). Verify symbols against the installed package/source before relying on them.
Use this skill when
- The top-level
cometchatdispatcher's Step 3.0 routes here becauseproduct === "voice-video"orchat-messaging+voice-video - The user explicitly asks for calling: "add calls", "add video calling", "add voice calls", "set up call screen", "integrate calls SDK", "add CallKit"
- The user is iterating on an existing chat integration and picks "Add calling" from the iteration menu (Step 7 of
cometchat)
This is the entry point for every calls integration. Do NOT invoke per-family cometchat-{family}-calls skills directly — this dispatcher detects the family, picks the integration mode (standalone vs additive), and routes to the right skill with the right preconditions.
Supported families:
| Family | Per-family skill | Calls SDK package | Stable today? |
|---|---|---|---|
| Web (React / Next.js / React Router / Astro) | cometchat-react-calls | @cometchat/calls-sdk-javascript | Yes |
| React Native (Expo + bare) | cometchat-native-calls | @cometchat/calls-sdk-react-native | Yes |
| Angular (12–15) | cometchat-angular-calls | @cometchat/calls-sdk-javascript | Yes |
| Android (V5 stable) | cometchat-android-v5-calls | com.cometchat:calls-sdk-android:5.x (Cloudsmith) | Yes — production-ready |
| Android (V6 GA) | cometchat-android-v6-calls | chatuikit-{compose,kotlin}-android:6.0.+ + explicit `com.cometchat:calls-sdk-android:5.0.+` peer dep (the "calls fold in" marketing is incomplete) | Yes — GA 2026-05-25, but ships broken OOTB: 5 required workarounds (W1–W5), incl. the peer dep + not using CometChatCallButtons. See per-family skill. |
| iOS (V5 stable) | cometchat-ios-calls | CometChatCallsSDK (SPM / CocoaPods) | Yes |
| Flutter (V5 stable) | cometchat-flutter-v5-calls (already exists) | cometchat_calls_sdk (Cloudsmith) | Yes |
| Flutter (V6 GA) | cometchat-flutter-v6-calls (already exists) | Calls fold into cometchat_chat_uikit: ^6.0.1 | Yes — GA 2026-05-25 (one explicit MaterialApp(navigatorKey: CallNavigationContext.navigatorKey) wiring required, see per-family skill) |
Status: All nine calls skills (this dispatcher + react, native, angular, ios, android-v5, android-v6, flutter-v5, flutter-v6) are authored and source-verified against each family's real SDK surface (decompiledcalls-sdk-androidAARs,CometChatCallsSDK.swiftinterface,@cometchat/calls-sdk-{javascript,react-native}.d.ts, and the Fluttercometchat_calls_*pub-cache). Android V5 ships 17 pre-authored sub-skills folded intocometchat-android-v5-calls/references/. Per-family caveats that are load-bearing: Android V6 needs 5 workarounds (W1–W5); Flutter V6 needs the `MaterialApp(navigatorKey: CallNavigationContext.navigatorKey)` wiring; iOS has no `CometChatCalls.login()` (auth is per-session viagenerateToken).
Clarification contract (ALL coding agents) — read `references/asking-questions.md`. Every "ask"/"AskUserQuestion" in this skill follows that contract: on any agent without a structured-question primitive, render the question as a numbered text list and WAIT for a real answer (never default/infer; auto-mode doesn't authorize skipping), map by value/label not position, and accept a free-text fallback.
Why a separate calls dispatcher
The chat dispatcher (cometchat/SKILL.md) assumes the user is adding a chat surface — its placement vocabulary, component catalog, framework skills, and verification checklist are all chat-shaped. Calls is a different surface area:
| Concern | Chat dispatcher | Calls dispatcher (this skill) |
|---|---|---|
| Packages | chat-uikit-* + chat-sdk-* | chat-sdk-* + calls-sdk-* (no UI Kit in standalone mode) |
| Init | Chat SDK init + login | Chat SDK init + login + Calls SDK CallAppSettings (the dual-SDK contract) |
| Placement question | "Where does chat live?" | "Where does the call trigger live?" + "Where do call logs live?" |
| Components | Conversations, MessageList, Composer, Users, Groups | CallButtons, IncomingCall, OutgoingCall, OngoingCall, CallLogs |
IncomingCall mount | At chat surface (rings while user is chatting) | At app root — always-rendering, listens app-wide |
| VoIP push (mobile) | Optional add-on | Mandatory in standalone mode — calls without ringing isn't a product |
| Background lifecycle | N/A unless calls feature added | Required — Android foreground service / iOS CallKit + PushKit |
The two dispatchers share cometchat-{family}-core (init + login + env + theme tokens) but diverge on everything else.
How v4.1 calls integration works
Same conversational model as the chat dispatcher — detect, gather, plan, write, verify — but with a calls lens.
Steps
Step 1 — Detect family + mode
If the user came from the top-level cometchat dispatcher, framework + credentials are already in .cometchat/config.json. Skip detection and read:
npx @cometchat/skills-cli config show --jsonIf invoked directly (e.g. user typed "add calls to my app"), run:
npx @cometchat/skills-cli detect --jsonSame family detection as the chat dispatcher (reactjs, nextjs, react-router, astro, expo, react-native, angular, android, flutter, ios).
Pick the integration mode by looking at the project state:
| Signal | Mode | What runs |
|---|---|---|
.cometchat/state.json exists with chat surfaces wired | Additive — calls layered onto existing chat | Per-family -calls skill in additive section. Patches the existing provider to mount IncomingCall at root. Adds call buttons inline on chat surfaces. Optional VoIP push. |
.cometchat/state.json does NOT exist (or has no chat surfaces) AND product === "voice-video" | Standalone — calls is the product | Per-family -calls skill in standalone section. No chat UI Kit. CallButtons + IncomingCall + OngoingCall + CallLogs. VoIP push wired, not optional. /calls route or platform equivalent for call logs. |
| Neither — user wants calls but it's ambiguous | Ask | AskUserQuestion: "Are you adding calling alongside an existing chat integration, or building a calling-first app?" |
Step 2 — Verify credentials
Calls reuses Chat SDK credentials. If .env (web/RN), local.properties (Android), Secrets.swift (iOS), lib/cometchat_config.dart (Flutter), or src/environments/environment.ts (Angular) already has appId / region / authKey, skip credential setup.
If credentials are missing, hand back to cometchat/SKILL.md Step 2 — credential provisioning is the same regardless of product. Do not duplicate the auth flow here.
Step 3 — Gather calls-specific requirements
Step 3.0 — Calling mode (Ringing vs Session) ★
This is the first calls-specific question. The Calls SDK supports two fundamentally different UX shapes — pick before anything else, because the rest of Step 3 + the per-family scaffold differ.
Resolve in priority order:
1. Infer ONLY from explicit signals. Don't default to either mode. The user must clearly indicate one of the two flavors before you skip the prompt:
| User said something like… | Mode |
|---|---|
| "1:1 call", "user to user call", "phone call", "ring my friend", "call a contact", "FaceTime-like", "WhatsApp-style call" | Ringing |
| "group call between friends", "conference call up to N people" (small group, one initiator who rings) | Ringing |
| "meeting", "Google Meet", "Zoom-like", "scheduled meeting", "join with a link", "meeting room", "conference room", "webinar", "town hall", "virtual classroom" | Session |
| "huddle" (Slack-style — instant join from a channel) | Session |
| Just "calls", "calling", "integrate calls", "add calls", "set up calling", "voice/video calls" with NO further context about ringing-vs-meeting | AMBIGUOUS — ASK (do NOT default) |
| Mixed signals ("calls and meetings", "phone calls or video conferencing") | AMBIGUOUS — ASK |
The bottom rows are the load-bearing ones. Generic phrases like "integrate calls" or "I want calling in my app" are NOT a signal for Ringing — they're a signal that the user hasn't told you yet. Ask.
If you DO have a clear signal, confirm with one line so the user can redirect:
"Got it — setting up Ringing (1:1 / group calls with an incoming-call screen). If you wanted meeting-room URLs instead, say so and I'll switch."
2. Use Tier 4 use-case context if available. Use-cases telegraph the mode:
| Use case | Mode |
|---|---|
| Telehealth provider/patient (1:1 visit) | Ringing |
| Marketplace buyer ↔ seller | Ringing |
| Customer support (agent ↔ customer) | Ringing |
| Broadcast / webinar / town hall | Session |
| Team huddle / standup | Session |
3. Only if you can't tell — ask. Use this prompt verbatim — do NOT rephrase, do NOT swap options. Option 1 is "Session"; option 2 is "Ringing":
- question: "What kind of calling experience are you building?"
- header: "Calling mode"
- multiSelect: false
- options (display in this exact order — Session FIRST, Ringing SECOND):
1. label: "Session — meeting / conference room", description: "Multiple users join the same session by ID or link. No ringing. Like Google Meet, Zoom, or a Slack huddle. Examples: scheduled meetings, webinars, team huddles, classroom calls." 2. label: "Ringing — 1:1 or group calls", description: "One user calls another (or a small group). Recipient's device rings; they accept or decline. Like a phone call. Examples: WhatsApp, FaceTime, in-app voice/video calls between contacts."
Strict-order rule: render option 1 above option 2. Do not reorder. Session-first is standardized across CometChat product surfaces.
Map to mode value:
- "Ringing" →
mode === "ringing" - "Session" →
mode === "session"
This drives which per-family reference loads at Step 6:
| Mode | Reference (per-family) | Reference (Android V5) |
|---|---|---|
ringing | cometchat-{family}-calls/references/ringing-integration.md | cometchat-android-v5-calls/references/ringing-integration.md |
session | cometchat-{family}-calls/references/call-session.md | cometchat-android-v5-calls/references/join-session.md |
Note on the kit's prebuilt UI ("Standard" mode in earlier docs): the UI Kit's<CometChatCallButtons />+<CometChatIncomingCall />+<CometChatOngoingCall />are a kit-provided implementation of Ringing, not a third mode. If the user picks Ringing AND wants the kit's prebuilt UI (most common), the per-family scaffold uses kit components and treats the ringing reference as the architectural ground-truth. If they want custom UI on top of the chat-SDK signaling, the ringing reference is the implementation guide.
⚠️ Group calls do NOT use the Ringing channel (validated 2026-05-15, applies to all kits). The kit's<CometChatCallButtons group={g}>sends a custom meeting message to the group viaCometChat.sendCustomMessagewithtype: "meeting", NOTCometChat.initiateCall. Other group members receive the meeting card in<CometChatMessageList />(kit-based receivers) OR aMessageListener.onCustomMessageReceivedwithcategory: CATEGORY_CUSTOM+type: "meeting"(custom-UI receivers). TheCallListener.onIncomingCallReceivedchannel only fires for 1:1 user calls. If the user wants group calls, loadreferences/group-calls.mdIN ADDITION TO ringing-integration.md — group has its own architecture even though customers think of it as "ringing for groups."
Step 3.1 — Call trigger placement (where the call starts from)
Different from the chat dispatcher's Step 3a archetype question. Ask placement-intent on the calls surface:
Ask the user (preserve the structured shape — question/header/multiSelect/options[].label/options[].description):
- question: "How should users start a call in your app?"
- header: "Call trigger"
- multiSelect: false
- options (the option set differs by mode — show only the rows matching
modefrom Step 3.0):
If `mode === "ringing"` — show these: 1. label: "Call button on user/contact profile", description: "Voice + video buttons next to a user's name. Best for marketplaces, social, dating, healthcare." 2. label: "Always-on '/calls' route", description: "Dedicated calls page with a 'Start a call' picker and call history. Best for telehealth, virtual events, contact centers." 3. label: "Embedded in chat surface", description: "Call buttons in the chat header — voice + video next to message threads. Best for apps that already have chat. (Additive mode only.)" 4. label: "Floating widget", description: "Always-visible call bubble — like Zoom's floating window. Best for support / sales tools."
If `mode === "session"` — show these instead: 1. label: "Meeting link route ('/meet/:sessionId')", description: "Shareable URLs that anyone with the link can join. Best for scheduled meetings, webinars, classrooms." 2. label: "Channel-bound huddle", description: "Session keyed to a chat channel/group ID — anyone in the channel can join the active huddle. Best for team collab apps." 3. label: "Lobby / waiting-room route", description: "User lands on a waiting screen, host admits. Best for telehealth, customer support." 4. label: "Custom — I'll wire it myself", description: "Just give me the SDK glue. I'll decide the surface."
The chosen option drives which per-family -calls skill section to load and what to scaffold.
Then ask about call logs separately — call logs are usually a different surface from the trigger:
- question: "Where should call history live?"
- header: "Call logs"
- options:
1. label: "Dedicated /calls route (or screen)", description: "Recommended — full call history, filterable, jump to caller's profile." 2. label: "Inside the call trigger surface", description: "Compact list under the call button. Best when call volume is low." 3. label: "Don't show call history", description: "Skip — calls just happen, no log surface."
Step 4 — Detect mandatory mobile prerequisites
For mobile families (android, ios, expo, react-native, flutter), VoIP push is non-negotiable in standalone mode — without it, missed calls don't ring. Detect what's already wired so the per-family -calls skill knows what to add and what to skip:
Android:
- Firebase Cloud Messaging — look for
google-services.jsoninapp/andid("com.google.gms.google-services")inapp/build.gradle{.kts} - Service registration — look for
<service android:name=".YourFirebaseMessagingService" ...>inAndroidManifest.xml - ConnectionService — look for
android.permission.MANAGE_OWN_CALLSandBIND_TELECOM_CONNECTION_SERVICEinAndroidManifest.xml - Foreground service type — look for
<service android:foregroundServiceType="phoneCall|microphone|camera">(Android 14+ silently crashes withoutphoneCall)
iOS:
- CallKit — look for
import CallKitand aCXProviderconfigured somewhere in source - PushKit — look for
import PushKitand aPKPushRegistrylistener (VoIP push token registration) - Capabilities — look for
Background Modeswithvoip+audio+remote-notificationinInfo.plistandentitlements - Microphone + camera Info.plist usage strings
React Native:
react-native-callkeep(CallKit + ConnectionService bridge) inpackage.json@react-native-firebase/messaging(Android) and aPushNotificationIOSsetupreact-native-voip-push-notificationfor iOS VoIP push tokens
Flutter:
flutter_callkit_incominginpubspec.yaml, OR a platform-channel bridge to native CallKit/ConnectionServicefirebase_messagingfor FCM- Native
Info.plistandAndroidManifest.xmlentries (Flutter doesn't auto-add these)
Surface what's wired and what's missing. The per-family -calls skill writes the missing pieces — it never assumes they're already there.
Step 5 — Show the plan and get approval
Before writing anything, show the user exactly:
- Which packages will be installed
- Which files will be created vs modified vs left alone
- Where the call trigger will land (with the file path)
- Where call logs will land (or "skipped" if option 3 above)
- For mobile: what VoIP push artifacts will be added (FCM service, CallKit provider, foreground service registration, manifest permissions, Info.plist entries)
- What the user must do manually that the skill can't (Android: enable FCM in Firebase console; iOS: add VoIP Services certificate to App Store Connect; Flutter: bump
minSdkVersionif below platform floor)
Wait for explicit approval. Then load the per-family -calls skill and run its scaffold.
Step 6 — Hand off to the per-family skill
Load the matching skill from your context:
| Detected family | Skill to load |
|---|---|
reactjs / nextjs / react-router / astro | cometchat-react-calls |
expo / react-native | cometchat-native-calls |
angular | cometchat-angular-calls |
android + android_version === "v5" | cometchat-android-v5-calls |
android + android_version === "v6" | cometchat-android-v6-calls |
flutter + flutter_version === "v5" | cometchat-flutter-v5-calls (existing) |
flutter + flutter_version === "v6" | cometchat-flutter-v6-calls (existing) |
ios | cometchat-ios-calls |
Pass the gathered context: `mode` (ringing | session from Step 3.0), standalone vs additive (Step 1), trigger placement (Step 3.1), log placement, mobile prereqs status.
The per-family skill loads the matching mode reference as its primary blueprint:
mode | Reference loaded inside the per-family skill |
|---|---|
ringing (1:1 user calls) | references/ringing-integration.md (Android V5: same name) |
session (meeting-room URLs, no chat-side signaling) | references/call-session.md (Android V5: references/join-session.md) |
Additionally — if groupCalls === true in the gathered context (the user mentioned "group calls", "team meetings", "huddles", or selected group as a call target in Step 3.1), ALSO load `references/group-calls.md` from the per-family skill. Group calls use a different signaling channel than 1:1 ringing — they broadcast a custom "meeting" message instead of CometChat.initiateCall. Loading only ringing-integration.md produces code that works for 1:1 but silently breaks for groups on custom-UI receivers. The group-calls.md reference exists for all 8 families. See the Step 3.0 disclosure above for the underlying semantic.
The per-family skill writes code; this dispatcher does not.
If the per-family skill isn't loaded into the agent's context (i.e. the user installed @cometchat/skills before v4.1 and never re-ran npx @cometchat/skills add --family <family>), tell the user once:
"I just installed the {family} calls skill into your workspace. Please re-run/cometchat-callsto continue — your config is saved in.cometchat/config.json, so the next run picks up at code generation without re-asking the placement questions."
Same hot-reload constraint as the chat dispatcher: agents snapshot the skill set at session start and don't reload mid-session. Continuing this session would mean writing calls code from training memory — which the per-family skill explicitly forbids.
Step 7 — Verify
After the per-family skill writes code, run:
npx @cometchat/skills-cli verify --calls --json(Verifier flag --calls is added in CLI 2.4.0 alongside this dispatcher; it checks for the dual-SDK init, IncomingCall root mount, VoIP push wiring on mobile, and the call trigger / logs placements the user picked.)
If verify passes, drop the user into the iteration menu — same shape as the chat dispatcher's Step 7, with calls-specific options:
- Add a feature (recording, screen sharing, PiP, in-call chat, custom UI)
- Customize call screen / participant list / control panel
- Set up production auth (server-minted tokens, no Auth Key in calls flow)
- Run diagnostics
Hard rules (every per-family -calls skill must enforce these)
These are the production-grade non-negotiables. The dispatcher checks them in Step 5's plan and the per-family skill is responsible for writing them:
1. Dual-SDK contract. Chat SDK does initiateCall (sends a MessageType.CALL); Calls SDK does joinSession. They are NOT interchangeable. The Android skill leads with this — agents trained on multi-platform CometChat data will conflate com.cometchat.chat.core.Call with com.cometchat.chat.models.Call and write code that compiles but silently breaks. 2. VoIP push wired in standalone mode, not documented. CallKit + PushKit (iOS), ConnectionService + FCM high-priority (Android), react-native-callkeep (RN), flutter_callkit_incoming (Flutter). Documentation isn't enough — without working push, missed calls don't ring. 3. Foreground / background lifecycle correct. Android 14+ silently crashes on wrong foregroundServiceType for ongoing calls; iOS requires CallKit reporting for background audio. Belongs in the -calls skill body, not a "see also" reference. 4. Server-minted auth tokens for the calls path. Production calls flows must use auth tokens, never Auth Key. The skill detects existing token endpoints (same way the chat dispatcher does) and uses loginWithAuthToken(). 5. Cleanup on hangup. Camera-light-stays-on / mic-stays-hot is the canonical "looks fine in dev, fails review" bug. Every per-family skill includes the explicit teardown checklist. 6. Permissions with rationale. Microphone, camera, screen-record (where supported), notifications. All four, all with usage strings. 7. `IncomingCall` mounted at app root in standalone mode. Not at the chat surface — calls is the whole product, the listener has to be alive everywhere. 8. Honor "SDK only" when the user says SDK only (ENG-35711). If the developer explicitly asks for the Calls SDK without UI Kit components — phrasing like "pure SDK," "no UI Kit components," "build my own call UI," "SDK-only path" — the per-family skill MUST NOT scaffold CometChatCallButtons, CometChatIncomingCall, CometChatOutgoingCall, or CometChatOngoingCall. Use Chat SDK CometChat.initiateCall for signaling + Calls SDK joinSession (and a hand-written button/screen) for the surface. Never silently fall back to UI Kit components "because they're easier" — that's a documented frustration from two testers. If the developer's request can't be served without a UI Kit component, ask explicitly: "This piece needs the kit's `<CometChatXxx>` — happy to use it, or do you want a hand-written equivalent?" Don't decide for them.
Anti-patterns
1. Don't try to add `apply-feature calls` from this dispatcher. That CLI command is the v4 chat-flow path — it returns the npm install command and that's it. This dispatcher does the actual integration end-to-end. If the user is in chat-messaging+voice-video mode, the chat flow already ran; just enter additive mode here. Never call apply-feature calls from inside this skill. 2. Don't load `cometchat-components` or `cometchat-placement` (the chat catalogs). Those are chat-shaped. The per-family -calls skill has its own component list (CallButtons, IncomingCall, OutgoingCall, OngoingCall, CallLogs) and its own placement vocabulary (call trigger, log surface, root listener). Loading the chat catalogs leaks chat assumptions into calls code. 3. Don't ask the chat archetype question ("Messaging app / Marketplace / SaaS / …"). That taxonomy is for where chat lives. Calls placement is where the call trigger lives + where logs live + whether VoIP push is mandatory. Different vocabulary, different recommendations. 4. Don't skip VoIP push in standalone mode even if the user says "I'll add it later." Calls without ringing isn't a product. The dispatcher refuses standalone-mode scaffold without VoIP push wired (or at minimum scaffolded with a clear TODO and red-banner warning in the call screen). 5. Don't mix V5 and V6 cohorts (Android, Flutter). The Calls SDK coordinates differ — V5 Android ships calls-sdk-android as a separate Cloudsmith dep; V6 folds calls into the unified chatuikit-{compose,kotlin}-android package. Same for Flutter. Read android_version / flutter_version from .cometchat/config.json and route to the matching cohort skill.
Ground truth references
Per-family -calls skills cite from these sources. The dispatcher itself doesn't write code; the per-family skills do. To verify which SDK signatures the skills target, check each family's published SDK artifact + public docs (the source-of-truth — an earlier draft pointed at a local ~/Downloads/calls-sdk/ clone that no longer exists; use the installed/published packages instead):
- Android V5:
com.cometchat:calls-sdk-android:5.x(decompile the Maven AAR, orjavapthe Gradle-cached classes) + docs. The 17 sub-topics are folded intocometchat-android-v5-calls/references/{recording,screen-sharing,picture-in-picture,background-handling,voip-calling,audio-controls,video-controls,participant-management,custom-ui,in-call-chat,call-logs,session-settings,event-listeners,join-session,ringing-integration,setup}.md. - iOS V5:
CometChatCallsSDK5.x — the.swiftinterfaceinside the resolved framework (SPM.build/artifacts/…or CocoaPodsPods/…; it ships vendored insideCometChatUIKitSwiftfor additive) + docs. - JavaScript (web):
@cometchat/calls-sdk-javascript@5— the package.d.ts(node_modules/@cometchat/calls-sdk-javascript/dist/index.d.ts) + docs. - React Native:
@cometchat/calls-sdk-react-native@5— the package.d.ts+@cometchat/calls-lib-webrtc(Cloudsmith) + docs. - Flutter:
cometchat_calls_sdk(V5: pairedcometchat_calls_uikit:^5.0; V6: folded intocometchat_chat_uikit:^6.0) — the pub-cache source — plus thecometchat-flutter-v5-calls/cometchat-flutter-v6-callsskills (audited against it). - Angular: the same JS Calls SDK (
@cometchat/calls-sdk-javascript) wrapped in Angular Inputs/Outputs — verify via@cometchat/chat-uikit-angular.d.ts+ the JS SDK.d.ts.
Two calling modes (Ringing + Session) — and the kit's prebuilt UI for Ringing
Naming clarification (ENG-35699 follow-up): This section labels three rows below — "Standard", "Ringing", and "Call Session" — but architecturally there are only TWO modes: Ringing (signaling-driven) and Session (URL-driven). "Standard" is the kit's prebuilt UI for Ringing (using<CometChatCallButtons>+<CometChatIncomingCall>), not a third architectural mode. The Step 3.0 prompt above asks Ringing vs Session — that's the load-bearing decision. The Standard row is the most-common rendering choice WITHIN Ringing mode.
CometChat supports two underlying calling workflows; the table below splits Ringing into "Standard (kit-prebuilt UI)" vs "Ringing (custom UI on signaling)" for routing purposes only:
| Row | Underlying mode | Driver | When to use | Per-family ref |
|---|---|---|---|---|
| Standard | Ringing | UI Kit (CometChatCallButtons + CometChatIncomingCall) | 80% case — chat-driven calls with prebuilt UI | covered in per-family SKILL.md |
| Ringing (custom UI) | Ringing | Chat SDK call entity + Calls SDK session | Custom incoming/outgoing call UI on top of CometChat signaling | references/ringing-integration.md |
| Call Session | Session | Calls SDK joinSession directly (no ringing) | Meeting-room URLs, scheduled calls, conference rooms | references/call-session.md |
Standard is the default Ringing rendering unless the user explicitly says "custom incoming call UI" (→ Ringing custom) or "meeting link / join with sessionId" (→ Call Session). The Tier 4 use-cases telegraph which mode they want — broadcast + team huddles use Call Session; telehealth + marketplace + support use Standard or Ringing custom.
Use-case integration patterns (Tier 4)
If the user describes their product clearly, route to the matching use-case reference for opinionated CallSettings + UX + compliance callouts:
- Telehealth / virtual visits →
references/use-case-telehealth.md— provider/patient flow, two-party consent recording, waiting room, HIPAA notes - Marketplace (buyer ↔ seller) →
references/use-case-marketplace.md— opaque UIDs, time-bounded access, anti-fraud heuristics - Customer support / help desk →
references/use-case-support.md— agent queue, auto-record for QA, post-call CSAT - Broadcast / webinar / town hall →
references/use-case-broadcast.md— locked SIDEBAR, mute-by-default attendees, raise-hand Q&A, RTMP bridge for 100+ - Team huddle / standup / collab →
references/use-case-team.md— TILE layout, opt-in join, recording auto-posted to channel
Each use-case doc covers end-to-end flow + recommended CallSettings + role-based UI + verification checklist. Apply on top of the per-family -calls skill, don't duplicate the architecture work.
Migration playbooks
For customers upgrading or layering calls on top of an existing chat install, every per-family calls skill ships:
references/migration-v4-to-v5.md— Calls SDK v4 → v5 (drop-in replacement; v5 APIs unlock granular event listeners + simpler init)references/add-calls-to-existing-chat.md— additive integration on top of an existing chat surface (init order, login order, IncomingCall mounting, server push setup)
Cite the right family. The web reference (cometchat-react-calls) is canonical for migration-v4-to-v5 — sister docs in other families add only family-specific deltas.
Server-side push templates (Tier 3)
VoIP push wiring is platform-specific. Three canonical templates ship under per-family references/:
- iOS APNs PushKit (.p8 token auth) —
cometchat-ios-calls/references/server-apns-pushkit.md - Android FCM HTTP v1 (data-only, priority HIGH) —
cometchat-android-v5-calls/references/server-fcm-voip.md(V6 sister with Compose IncomingCallActivity) - Web Push VAPID (best-effort browser ringing) —
cometchat-react-calls/references/server-web-push-vapid.md(Angular sister)
Hybrid platforms (RN, Flutter) ship references/server-push-bridge.md that routes to the right canonical by Platform.OS / Platform.is{iOS,Android}.
What this dispatcher does NOT cover
- The actual code. Per-family
-callsskills write the integration. This dispatcher routes. - AI-Agent or BYO-Agent flows. Different surface entirely; out of scope. The top-level
cometchatdispatcher's Step 3.0 routes those to docs. - Moderation features for calls (recording-with-consent prompts, profanity filter on in-call chat, etc.). Same status as the chat dispatcher — moderation is dashboard-only.
- Call analytics dashboards — the CometChat dashboard surfaces these, no integration work needed.
Pointer back to chat dispatcher
If the user invoked /cometchat-calls directly but the project has no integration at all yet (no .cometchat/config.json, no chat surfaces), and they pick the additive mode by mistake, redirect:
"Looks like you don't have CometChat integrated yet. For a calling-first app, that's fine — I'll run the standalone calls flow now. If you want chat alongside, run /cometchat first to set up chat, then come back to add calls."Don't auto-route to cometchat/SKILL.md from here — it's the user's call (no pun intended) which surface is primary.
Use case — Broadcast / Webinar
A presenter (or panel) addresses a large audience. Most attendees have audio + video off; a small set of presenters are visible. Q&A via raise-hand. Optional broadcast to YouTube/RTMP via server-side bridge.
Examples: Town halls, all-hands, product launches, conference sessions.
Scale note: CometChat Calls SDK supports up to ~50 simultaneous video streams per call (varies by plan). For 100+ broadcast use cases, consider streaming-protocol bridges (RTMP push to YouTube/Twitch) where most attendees consume via streaming, not bidirectional WebRTC.
---
End-to-end flow
1. Presenter creates a "Broadcast" group (CometChat group keyed to event ID)
↓
2. Server pre-creates the group with presenters as moderators
↓
3. Attendees join the group (group is public OR password-gated for paid events)
↓
4. Event time: presenter starts call → attendees auto-join (no incoming-call ring)
↓
5. By default: all attendees mic + camera OFF
Presenter has mic + camera ON
↓
6. Q&A: attendee taps "Raise hand" → presenter sees → presenter unmutes them
↓
7. Server-side: optional RTMP push streams the call to YouTube Live / Twitch / a CDN
↓
8. Presenter ends call → recording uploaded → notification + replay link sent to all attendees---
Recommended call settings
const settings = {
layout: "SIDEBAR", // Presenter dominant
hideChangeLayoutButton: true, // Don't let attendees change
hideRecordingButton: false, // Presenter records
autoStartRecording: true, // Always record broadcasts (replay value)
hideScreenSharingButton: false, // For slides
hideChatButton: false, // Q&A overflow + reactions
hideShareInviteButton: false, // Encourage sharing
hideRaiseHandButton: false, // Critical for Q&A
// Attendee-side: mic + camera off by default
joinWithMutedAudio: true,
joinWithMutedVideo: true,
idleTimeoutPeriodBeforePrompt: 86_400_000, // Disable idle timeout — events run long
};Presenters override joinWithMutedAudio/Video: false in their flow.
---
Roles + permissions
In the CometChat group, set scopes:
// Server-side, when creating the broadcast group:
await cometchatApi.createGroup({
guid: `event-${eventId}`,
members: [
{ uid: presenterUid, scope: "admin" },
...coPresenterUids.map((uid) => ({ uid, scope: "moderator" })),
// Attendees added on join (or batch-added pre-event for paid)
],
});Then enforce role-based UI:
function CallControls({ groupGuid, currentUserUid }: Props) {
const role = useUserRoleInGroup(groupGuid, currentUserUid);
const isPresenter = role === "admin" || role === "moderator";
return (
<div>
{isPresenter && <RecordingButton />}
{isPresenter && <ScreenShareButton />}
{!isPresenter && <RaiseHandButton />}
{/* Attendees see a stripped-down toolbar */}
</div>
);
}---
Raise-hand → unmute flow
// Presenter's view: see attendees with raised hands, tap to unmute
function PresenterRaisedHandsList() {
const [raised, setRaised] = useState<RaisedHand[]>([]);
useEffect(() => {
CometChatCalls.addEventListener("onParticipantHandRaised", (event: RaisedHand) => {
setRaised((prev) => [...prev, event]);
});
CometChatCalls.addEventListener("onParticipantHandLowered", (event: RaisedHand) => {
setRaised((prev) => prev.filter((p) => p.uid !== event.uid));
});
}, []);
async function unmuteParticipant(uid: string) {
// Send a custom message asking the participant to unmute themselves
// (CometChat doesn't support force-unmute — privacy).
// There is NO CometChatCalls.lowerParticipantHand(uid) — the SDK only
// exposes lowerHand() for the LOCAL user. To lower someone else's hand,
// message them and let their client call lowerHand() (see raise-hand.md).
const msg = new CometChat.CustomMessage(
uid, CometChat.RECEIVER_TYPE.USER, "lower_hand", {},
);
await CometChat.sendCustomMessage(msg);
// Optimistically drop them from our local raised-hands list
setRaised((prev) => prev.filter((p) => p.uid !== uid));
}
return (
<ul>
{raised.map((p) => (
<li key={p.uid}>
{p.name}
<button onClick={() => unmuteParticipant(p.uid)}>Allow to speak</button>
</li>
))}
</ul>
);
}---
RTMP bridge for streaming
For broadcasts beyond CometChat's WebRTC capacity, server-side RTMP push:
// Server-side: when broadcast starts, spin up an RTMP bridge that joins
// the call as a "viewer" and pushes the composited stream to YouTube Live.
// Use a service like Daily.co's RTMP-out, AWS IVS, or roll your own with
// FFmpeg + headless Chrome.
//
// CometChat does not ship an RTMP bridge — this is application infrastructure.
async function startRtmpBridge(eventId: string, rtmpUrl: string) {
await rtmpBridgeService.start({
sessionId: eventId,
rtmpDestination: rtmpUrl,
// Bridge joins with a service-account UID
serviceAccountUid: `bridge-${eventId}`,
});
}---
Anti-patterns
1. No `joinWithMutedAudio: true` for attendees. 50 people unmuted at once = chaos. 2. No role differentiation. Attendees can record / kick / share-invite → vandalism. 3. Same SDK call for 200+ attendees. WebRTC degrades. Use RTMP bridge for large audiences. 4. No idle timeout disable. Default 30min idle ends a 60min event mid-Q&A. 5. Recording broadcasted to YouTube without disclosure. GDPR / CCPA: attendees must know they're being recorded for public distribution. 6. No pre-event check. Presenter joins 5min before, mic doesn't work, panic. Have a "tech check" room they can join 30min ahead.
---
Verification checklist
- [ ] Group created server-side with presenter as admin
- [ ] Attendees join muted (audio + video)
- [ ] Layout locked to SIDEBAR
- [ ] Recording auto-on
- [ ] Idle timeout disabled
- [ ] Raise-hand → presenter approval flow wired
- [ ] Role-based UI (presenter sees recording/screenshare; attendees see raise-hand)
- [ ] RTMP bridge for 100+ attendees
- [ ] GDPR/CCPA recording notice in attendee-facing copy
- [ ] Tech check room available pre-event
---
Pointers
cometchat-react-calls/references/raise-hand.md— raise-hand referencecometchat-react-calls/references/recording.md— recording referencecometchat-react-calls/references/screen-sharing.md— for slidescometchat-react-calls/references/call-layouts.md— SIDEBAR layoutcometchat-react/SKILL.md— group scope + admin patterns
Use case — Marketplace (buyer ↔ seller)
A peer-to-peer marketplace where buyers and sellers communicate before/during a transaction. The shape: time-bounded chat between matched users, optional 1:1 video for product walkthroughs, communications archived for dispute resolution, identity controls so neither party can recontact post-transaction without going through the platform.
Examples: OfferUp, Airbnb host/guest messaging, eBay seller-buyer chat with video, Etsy custom-order discussions.
---
End-to-end flow
1. Buyer expresses interest in a listing
→ Server creates a CometChat group with both buyer + seller
→ Group lives only for the duration of the deal (auto-archive after N days)
↓
2. Either party can initiate text chat in the group
↓
3. (Optional) Either party can initiate a video call to walk through the product
→ Identity is hidden — call shows "Buyer" or "Seller", not real names
↓
4. Transaction closes (purchase confirmed / canceled)
→ Group is locked: read-only for both parties, archived for dispute resolution
↓
5. After dispute window (90 days typical), group is hard-deleted---
Identity protection
Don't expose real names/emails through the SDK. Use opaque UIDs that resolve to display strings only via your auth-gated lookup:
// On the server, when creating the deal group:
await cometchatApi.createGroup({
guid: `deal-${dealId}`,
members: [
{ uid: `buyer-${buyerId}`, scope: "participant" },
{ uid: `seller-${sellerId}`, scope: "participant" },
],
metadata: {
dealId,
expiresAt: Date.now() + 90 * 86400_000,
},
});
// Client-side: when CometChat returns a UID, resolve display name via your API
function resolveDisplayName(uid: string, viewerRole: "buyer" | "seller"): string {
// Only show "Seller" / "Buyer" — not real name
if (uid.startsWith("seller-")) return viewerRole === "buyer" ? "Seller" : "You";
if (uid.startsWith("buyer-")) return viewerRole === "seller" ? "Buyer" : "You";
return "Unknown";
}---
Recommended call settings
const settings = {
layout: "SPOTLIGHT", // 1:1 focus
hideChangeLayoutButton: true,
hideRecordingButton: true, // No platform-side recording — privacy + dispute via chat archive
hideShareInviteButton: true, // Buyer ↔ seller only — don't allow inviting
hideScreenSharingButton: false, // Useful for "show me the actual condition"
hideChatButton: false, // For sharing photos/details mid-call
hideRaiseHandButton: true,
idleTimeoutPeriodBeforePrompt: 600_000, // 10min idle = end (sometimes deals die)
};---
Time-bounded access
After the deal closes (or expires), revoke access without deleting history:
// Server-side, when transaction closes:
await cometchatApi.updateGroup({
guid: `deal-${dealId}`,
metadata: {
status: "closed",
closedAt: Date.now(),
},
});
// Lock by switching group to "private" with no joinable handle
await cometchatApi.kickMembers({ guid, uids: [/* all participants */] });Or — if your dispute window allows — keep them in the group as read-only by checking metadata.status client-side and disabling composer:
function DealMessageComposer({ group }: Props) {
const isClosed = group.metadata?.status === "closed";
return (
<CometChatMessageComposer group={group} disabled={isClosed} />
// (Disable shows the input greyed out with explanation)
);
}---
Anti-patterns
1. Using real user UIDs in CometChat. Real names leak via SDK events. Always use opaque IDs. 2. Allowing share-invite. Bypasses identity controls — a savvy user shares the call link to a third party (their lawyer, advocate, etc.). Lock down to invitees only. 3. No expiration. Old deals' groups pile up. Use metadata.expiresAt + nightly cron to delete. 4. Recording calls. Liability risk — neither party gave consent + you may not be the legal data controller for their home interiors. 5. Cross-platform message routing without checks. Customer says "find me on WhatsApp" → links pasted in chat → bypass your platform's protection. Run profanity-filter + URL-detection extensions; flag attempts.
---
Anti-fraud heuristics
// Hook into message-sent events
CometChat.addMessageListener("antifraud", new CometChat.MessageListener({
onTextMessageReceived: (msg) => {
const text = msg.getText();
// Off-platform routing attempts
const offPlatform = /\b(whatsapp|telegram|signal|wechat|email|@gmail|@yahoo)\b/i;
// Wire-fraud red flags
const wireFraud = /\b(western union|moneygram|gift card|crypto|bitcoin|wire transfer)\b/i;
if (offPlatform.test(text) || wireFraud.test(text)) {
// Quietly flag for review — don't tip the user that you're watching
api.flagMessage({ messageId: msg.getId(), reasons: { offPlatform: offPlatform.test(text), wireFraud: wireFraud.test(text) }});
}
},
}));---
Verification checklist
- [ ] Opaque UIDs used (no real names in CometChat)
- [ ] Display names resolved via auth-gated API
- [ ]
hideShareInviteButton: true - [ ] No platform-side recording
- [ ] Group
metadata.expiresAtset + nightly archive job - [ ] Anti-fraud message listener wired
- [ ] Profanity filter + URL detection extensions enabled
- [ ] Call settings: SPOTLIGHT layout, idle 10min
---
Pointers
cometchat-react-calls/references/screen-sharing.md— for product walkthroughcometchat-react-extensions— profanity-filter + URL-detectioncometchat-production— server-minted auth tokens (don't leak the auth key in client)cometchat-react/SKILL.md— group + message UX
Use case — Customer support / Help desk
A customer initiates contact with a support agent. The shape: customer arrives via "Talk to us" button, lands in a queue, gets matched to next available agent, voice-first (video optional), screen-share for troubleshooting, recording for QA + agent training, post-call CSAT survey.
Examples: Intercom voice/video upgrade, Zendesk Talk, custom help-desk integrations.
---
End-to-end flow
1. Customer clicks "Get help" in your app
→ Server checks queue depth → routes to next agent OR shows wait estimate
↓
2. Server creates a support group (CometChat group keyed to ticket ID)
Both customer + agent are members
↓
3. Agent gets push notification "New support call"
↓
4. Agent accepts → call starts; customer auto-joins
↓
5. Mid-call: agent can request screen-share to see the customer's screen
→ Customer must explicitly allow (OS-level permission)
↓
6. Agent ends call → recording auto-uploaded to your QA pipeline
↓
7. Customer gets CSAT survey (1-5 rating + comment)
→ Stored against the ticket for agent performance reviews---
Agent-side identity
Display the agent's first name + photo, NOT internal handle:
// Agent's CometChat user metadata (set when agent logs into the support tool):
await CometChat.updateUser(new CometChat.User({
uid: `agent-${employeeId}`,
name: "Sarah", // First name only — privacy + warmth
metadata: {
displayPhoto: "/agent-photos/sarah.png",
role: "Support Specialist",
department: "Billing",
},
}));---
Recommended call settings
const settings = {
layout: "SPOTLIGHT", // Speaker focus
hideChangeLayoutButton: false, // Agent may want grid for note-taking workflows
hideRecordingButton: true, // Don't let agent toggle — server-side auto-record
autoStartRecording: true, // QA recording is the policy
// (consent text in the in-app "By starting the call,
// you agree to recording for quality" notice)
hideScreenSharingButton: false, // Critical for troubleshooting
hideChatButton: false, // For pasting links/error messages
hideShareInviteButton: true, // Customer ↔ agent only
hideRaiseHandButton: true,
idleTimeoutPeriodBeforePrompt: 300_000, // 5min — short, agents are busy
};---
Recording consent (one-party + platform notice)
For most customer-support flows, a "By starting this call, you agree to recording" notice in your app's pre-call screen is sufficient (one-party consent jurisdictions). For two-party-consent jurisdictions (CA, IL, FL), add an explicit consent dialog before connecting:
function PreCallScreen({ onAccept, onCancel }: Props) {
return (
<dialog open className="precall">
<h2>Connect to support</h2>
<p>Estimated wait: 2 minutes</p>
<div role="note" aria-label="Recording notice">
<p><strong>This call will be recorded for quality assurance.</strong></p>
<p>By starting the call, you consent to recording. Recordings are
retained for 90 days, accessible to you on request, and never sold.</p>
</div>
<button onClick={onAccept}>Start call</button>
<button onClick={onCancel}>Cancel</button>
</dialog>
);
}---
Queue display + ETA
function QueueWaiting({ ticketId }: Props) {
const [position, setPosition] = useState<number>(0);
const [eta, setEta] = useState<number>(0);
useEffect(() => {
// Server-side queue manager publishes updates via CometChat custom messages
const listener = CometChat.addMessageListener("queue-updates", new CometChat.MessageListener({
onCustomMessageReceived: (msg) => {
const data = msg.getCustomData();
if (data.ticketId === ticketId) {
setPosition(data.position);
setEta(data.etaSeconds);
}
},
}));
return () => CometChat.removeMessageListener("queue-updates");
}, [ticketId]);
return (
<div role="status" aria-live="polite">
<p>You're number {position} in line</p>
<p>Estimated wait: {Math.round(eta / 60)} minutes</p>
</div>
);
}---
Post-call CSAT
Fire immediately after call end; before they navigate away:
function PostCallSurvey({ ticketId }: Props) {
const [rating, setRating] = useState<number | null>(null);
return (
<dialog open>
<h2>How was your call?</h2>
<fieldset>
<legend>Rate from 1 (poor) to 5 (excellent)</legend>
{[1, 2, 3, 4, 5].map((n) => (
<button key={n} onClick={() => setRating(n)} aria-pressed={rating === n}>
{n} ⭐
</button>
))}
</fieldset>
<textarea name="comment" placeholder="Anything else?" />
<button onClick={() => api.submitCsat({ ticketId, rating, comment })}>Submit</button>
</dialog>
);
}---
Anti-patterns
1. No recording notice. Two-party-consent law: lawsuit risk. 2. Letting agent disable recording mid-call. QA pipeline breaks. Hide the recording button server-side; only ops can disable for specific tickets via admin tool. 3. Sharing internal employee handles. Customer doesn't need agent's full name or company username — just first name. 4. Recording forever. Set 90-day retention; auto-delete via storage lifecycle. 5. No queue UX. Customer waits 5 minutes thinking it's broken → bounces. Show position + ETA. 6. Agent transfers without context. When transferring to another agent, pass the ticket history (custom messages with summary). Customer hates re-explaining.
---
Verification checklist
- [ ] Recording is server-side auto-on; agent can't disable
- [ ] Pre-call screen shows recording notice + estimated wait
- [ ] Queue position + ETA visible during wait
- [ ] Customer ↔ agent only — share-invite hidden
- [ ] Idle timeout 5min (short on purpose)
- [ ] Post-call CSAT fires immediately
- [ ] Recording retention enforced via storage lifecycle (90d default)
- [ ] Agent transfer carries ticket context
---
Pointers
cometchat-react-calls/references/recording.md— recording configcometchat-react-calls/references/screen-sharing.md— for troubleshootingcometchat-react/SKILL.md— group + custom message patternscometchat-production— credential hygienecometchat-react-extensions— analytics for agent QA
Use case — Team / Standup / Collaboration
Internal team meetings — standup, sprint review, retro, ad-hoc collaboration. The shape: 2–15 participants, all with mic + video on, TILE layout for equal visibility, screen-share for shared docs, in-call chat for links, optional recording for absent teammates.
Examples: Slack huddles, Microsoft Teams meetings, internal company calls.
---
End-to-end flow
1. A user creates or joins a team channel (CometChat group already exists for the team)
↓
2. Anyone in the group can start a call → all members get a "huddle started" notification
↓
3. Members join voluntarily (no incoming-call ring — opt-in)
↓
4. Mic + camera ON by default (it's a team meeting, not a webinar)
↓
5. (Optional) Anyone can record; absent members get the link in the channel
↓
6. Screen-share + in-call chat for collaboration
↓
7. Last person leaves → call ends → optional recording posted to channel---
Recommended call settings
const settings = {
layout: "TILE", // Equal visibility — team meetings
hideChangeLayoutButton: false, // Let users pick (some prefer SPOTLIGHT)
hideRecordingButton: false, // Anyone can record; team norms decide
autoStartRecording: false,
hideScreenSharingButton: false, // Critical
hideChatButton: false, // Side-chat for links
hideShareInviteButton: false, // Pull others in mid-call
hideRaiseHandButton: false, // For larger team meetings (10+)
idleTimeoutPeriodBeforePrompt: 1_800_000, // 30min — meetings can have lulls
};---
"Huddle started" notification
// When someone starts a call in a team group, fire a custom message
// so other members see "🟢 Huddle started by Sarah" in the channel.
async function startHuddle(group: CometChat.Group) {
// 1. Mint a session via the Chat SDK. A Call returned by initiateCall carries
// a server-issued sessionId — getSessionId() (chat-sdk Call.getSessionId).
// There is NO object-form CometChatCalls.startSession({sessionId,participants}).
const call = new CometChat.Call(
group.guid,
CometChat.CALL_TYPE.VIDEO,
CometChat.RECEIVER_TYPE.GROUP,
);
const initiatedCall = await CometChat.initiateCall(call); // returns Call
const sessionId = initiatedCall.getSessionId();
// 2. Post a custom message in the chat group so members see the huddle + can join
const customMessage = new CometChat.CustomMessage(
group.guid,
CometChat.RECEIVER_TYPE.GROUP,
"huddle_started",
{
sessionId,
startedBy: currentUserUid,
startedAt: Date.now(),
},
);
await CometChat.sendCustomMessage(customMessage);
// 3. The starter joins the session. Each joiner does the same with the shared
// sessionId: generateToken(sessionId) → joinSession(token, settings, container).
// (calls-sdk joinSession(callToken, SessionSettings, HTMLElement) is canonical;
// the old startSession(callToken, callSettings, container) is deprecated.)
const { token } = await CometChatCalls.generateToken(sessionId);
await CometChatCalls.joinSession(token, sessionSettings, callContainer);
}Render in the chat list:
function HuddleStartedBubble({ message }: Props) {
const data = message.getCustomData();
return (
<div role="alert" className="huddle-bubble">
<p>🟢 Huddle started by {resolveUserName(data.startedBy)}</p>
<button onClick={() => joinHuddle(data.sessionId)}>Join</button>
</div>
);
}---
Recording → channel post
When the call ends and a recording was made, auto-post a link to the channel:
// Server-side: triggered by CometChat call-ended webhook
app.post("/webhooks/call", async (req, res) => {
if (req.body.trigger === "call_ended" && req.body.data.recordingUrl) {
const { sessionId, recordingUrl } = req.body.data;
const groupGuid = sessionId.split("-").slice(0, 2).join("-").replace("huddle-", "");
// Post a custom message in the team's chat group
await cometchatApi.sendCustomMessage({
receiver: groupGuid,
receiverType: "group",
type: "huddle_recording",
data: {
recordingUrl,
durationSeconds: req.body.data.durationSeconds,
},
});
}
res.status(200).end();
});---
Anti-patterns
1. Force-ringing all team members. Annoying — they might be focused. Use channel notification + opt-in join. 2. Assuming a screen-share quality knob exists in SessionSettings. There is no screenShareQuality field on SessionSettings (verified against the calls-sdk types) — don't promise a non-API setting. Screen-share resolution is negotiated by the browser/WebRTC; tune the source (share a tab vs full screen) rather than a SessionSettings field. 3. Idle timeout too short. Standup runs 35min, idle of 600s ends it during a debate. Use 1800s (30min). 4. Recording auto-on without team norms. Some teams hate being recorded; surprise recording = trust hit. Default off; let team decide. 5. No way to invite outside team mid-call. Sometimes you need to pull in a designer or PM. Don't hide share-invite for team calls. 6. Persistent sessions. A "huddle" session that lasts forever bills forever. Auto-end when last person leaves.
---
Auto-end on empty
// Subscribe to participant-left events; when count hits 0, end session
let participantCount = 0;
CometChatCalls.addEventListener("onParticipantJoined", () => participantCount++);
CometChatCalls.addEventListener("onParticipantLeft", () => {
participantCount--;
if (participantCount === 0) {
CometChatCalls.endSession();
}
});---
Verification checklist
- [ ] Mic + camera ON by default for joiners
- [ ] Layout TILE, but switchable
- [ ] Idle timeout 30min
- [ ] "Huddle started" custom message + Join button in channel
- [ ] Recording optional (not auto-on)
- [ ] Recording auto-posted to channel as link if made
- [ ] Auto-end when last participant leaves
- [ ] Share-invite available for pulling in non-team members
---
Pointers
cometchat-react-calls/references/call-layouts.md— TILE layoutcometchat-react-calls/references/in-call-chat.md— side chatcometchat-react-calls/references/screen-sharing.md— for shared docscometchat-react/SKILL.md— group + custom message patterns
Use case — Telehealth (provider ↔ patient)
A virtual visit between a healthcare provider and a patient. The shape: pre-call waiting room, provider initiates the call, video-first, screen-share for sharing labs/imaging, in-call chat for sending links, optional recording with explicit consent, post-call note-taking workflow.
Compliance: HIPAA (US) + PIPEDA (CA) + GDPR (EU) — patient data in any form (incl. recording, screen-share content) is regulated. Telehealth integrations carry compliance risk that's outside CometChat's scope. Always:
- Run a Business Associate Agreement (BAA) with CometChat
- Run a separate BAA with your storage provider (S3, GCS) for recordings
- Surface explicit consent UI before recording starts
- Implement HIPAA-compliant audit logging
The architecture below is a reference shape — pair it with your legal/compliance review before shipping.
---
End-to-end flow
1. Patient books appointment in your scheduling system
↓
2. At appointment time, both join a "virtual waiting room"
(CometChat group keyed to appointment ID)
↓
3. Provider sees patient is waiting, clicks "Start visit"
→ CometChat.initiateCall(patientUid, sessionId=appointmentId, type="video")
↓
4. Patient gets incoming call → accepts → in-call
↓
5. (Optional) Provider taps "Start recording" → consent prompt to patient
→ only on patient accept does setRecordingOnCallStart fire
↓
6. (Optional) Provider screen-shares lab results
↓
7. (Optional) Provider opens in-call chat to send PDF/links
↓
8. Provider ends call → recording uploaded to your secure storage
→ patient redirected to feedback form---
Recommended call settings (web/RN)
import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
const settings = {
layout: "SPOTLIGHT", // Focus on the person speaking
hideChangeLayoutButton: true, // Don't let user switch — calmer UX
hideRecordingButton: false, // Provider needs it
autoStartRecording: false, // NEVER auto-record without consent
hideScreenSharingButton: false, // Sharing labs is core
hideChatButton: false, // For sending links/PDFs
hideShareInviteButton: true, // Patient ↔ provider only — no inviting
hideRaiseHandButton: true, // 1:1 — no need
idleTimeoutPeriodBeforePrompt: 600_000, // 10min — long enough for thinking pauses
};---
Two-party consent recording
Two-party-consent jurisdictions (CA, IL, FL, MD, MA, MT, NH, PA, WA + Canada + EU) require explicit consent from BOTH parties before recording starts. Implement a banner, not just a small "REC" indicator:
function RecordingConsentBanner({ onAccept, onReject }: Props) {
return (
<div role="alertdialog" aria-labelledby="rec-title" className="rec-banner">
<h2 id="rec-title">Recording requested</h2>
<p>
Your provider would like to record this visit for medical records.
The recording will be stored securely and accessible to you in your
patient portal.
</p>
<button onClick={onAccept}>I consent to recording</button>
<button onClick={onReject}>I do not consent</button>
</div>
);
}
// Provider's "Start recording" button:
async function requestRecording() {
// 1. Send a CometChat custom message asking for consent
await CometChat.sendCustomMessage(/* type: 'recording_request', target: patientUid */);
// 2. Wait for patient response (custom message back: 'recording_consent_granted')
// 3. ONLY THEN call CometChatCalls.startRecording()
}---
Waiting room pattern
function WaitingRoom({ appointmentId }: Props) {
const [providerOnline, setProviderOnline] = useState(false);
useEffect(() => {
// Subscribe to the appointment group; provider's presence triggers visit start
const groupGuid = `appt-${appointmentId}`;
const listener = CometChat.addGroupMemberListener(/* fires when provider joins */);
return () => CometChat.removeGroupMemberListener(listener);
}, [appointmentId]);
return (
<div role="status" aria-live="polite">
{providerOnline ? <p>Your provider is ready. The visit will start shortly.</p>
: <p>Please wait. Your provider will be with you soon.</p>}
<button onClick={cancelVisit}>Leave waiting room</button>
</div>
);
}role="status" + aria-live="polite" so a screen reader announces the state change without interrupting (vs assertive which interrupts).
---
Anti-patterns
1. Recording without explicit consent UI. Two-party-consent law violation = lawsuit risk + HIPAA non-compliance. 2. Storing recordings in default S3. Use a HIPAA-compliant tier with BAA + KMS encryption + audit logging. 3. No idle timeout. Patient or provider walks away → call runs forever → bills + privacy concern (if recording). 4. Sharing PHI in call settings. Don't put patientName in callerName — use opaque IDs in the SDK config; resolve display names client-side from your auth-gated user store. 5. Sending custom-message PII without encryption. CometChat E2E encryption is opt-in (Extensions) — turn it on for telehealth.
---
Verification checklist
- [ ] BAA executed with CometChat
- [ ] BAA executed with recording storage provider
- [ ] Recording UX includes explicit consent prompt + audit log
- [ ] Waiting room → call transition is automatic on provider initiate
- [ ] Idle timeout 10min
- [ ] Layout locked to SPOTLIGHT
- [ ] Share-invite hidden (1:1 only)
- [ ] No PII in SDK config or push payloads — only opaque IDs
- [ ] HIPAA audit log captures: visit start/end, recording start/stop, screen-share start/stop, file send
---
Pointers
cometchat-react-calls/references/recording.md— recording referencecometchat-react-calls/references/screen-sharing.md— screen sharecometchat-react-calls/references/in-call-chat.md— link/file sendingcometchat-react-calls/references/idle-timeout.md— idle timeout configcometchat-productionskill — credential hygiene for sensitive integrationscometchat-react-extensions— message encryption setup
Related skills
How it compares
Choose cometchat-calls when CometChat is already selected and the product needs presenter-led broadcast sessions rather than generic 1:1 calling.
FAQ
How many video streams does CometChat Calls support per call?
CometChat Calls SDK supports up to roughly 50 simultaneous video streams per call, varying by plan. cometchat-calls documents RTMP bridges to YouTube or Twitch when audiences exceed bidirectional WebRTC limits.
What is the broadcast flow in cometchat-calls?
cometchat-calls starts with a presenter creating a Broadcast group keyed to an event ID. Presenters stay visible while most attendees remain muted, and Q&A runs through raise-hand before optional RTMP export.
Is Cometchat Calls safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.