Moveris Meet — Session Lifecycle¶
A bot session moves through defined states from creation until the meeting ends. Understanding these states helps you monitor calls, handle lobby delays, and correlate webhook results with participants.
In plain terms
After you start a session, the bot is created, joins the call, and begins analyzing people on camera. You can track progress through status updates and receive a webhook for each participant when their check finishes.
Bot State Machine¶
stateDiagram-v2
[*] --> bot_created: POST /api/sessions
bot_created --> joining_call
joining_call --> in_waiting_room: Lobby
joining_call --> in_call_not_recording: Admitted
in_waiting_room --> in_call_not_recording: Host admits
in_call_not_recording --> in_call_recording: Video streaming
in_call_recording --> call_ended: Meeting ends
call_ended --> done
bot_created --> fatal: Error
joining_call --> fatal: Error
in_call_recording --> fatal: Error
joining_call --> stopped: DELETE /api/sessions/:id
in_waiting_room --> stopped: DELETE /api/sessions/:id
in_call_not_recording --> stopped: DELETE /api/sessions/:id
in_call_recording --> stopped: DELETE /api/sessions/:id
done --> [*]
fatal --> [*]
stopped --> [*] | Status | Description |
|---|---|
bot_created | Bot provisioned with the meeting provider; not yet in the call |
joining_call | Bot is connecting to the meeting |
in_waiting_room | Bot is in the lobby waiting for the host to admit it |
in_call_not_recording | Bot joined but video is not streaming yet |
in_call_recording | Bot is recording — frame collection and liveness analysis are active |
call_ended | The meeting ended or the bot was removed |
done | Session complete; no further analysis |
stopped | Session ended early via DELETE /api/sessions/:id — distinct from a natural call_ended → done end |
fatal | Unrecoverable error — check logs or contact Moveris support. Also set when a stale session expires: the bot leaves the meeting and SSE clients receive session-status with fatal so the UI unlocks without a hard refresh. |
How to observe status: SSE on GET /api/sessions/:id/events (authenticated) or GET /api/interview/:token/events (interviewer). You can also poll GET /api/sessions/:id. These are not the same as liveness verdict webhooks—see How It Works — Two Ways You Receive Updates.
Participant Analysis Lifecycle¶
The interviewer UI uses manual scan: participants appear as Ready to scan and analysis starts only when the operator triggers it (or uses Re-scan). Frames do not auto-accumulate in the background.
stateDiagram-v2
[*] --> Joined: Participant joins
Joined --> Waiting: Camera on, awaiting trigger
Waiting --> Buffering: Interviewer starts scan
Buffering --> Analyzing: N frames collected
Analyzing --> Complete: fast-check verdict
Buffering --> Stopped: Camera off, timeout, manual stop, or participant leaves
Stopped --> Buffering: scan / retry
Complete --> Waiting: Camera off > threshold, then on
Complete --> Buffering: Re-scan (retry)
Complete --> [*]: Participant leaves
Stopped --> [*]: Participant leaves | Stage | Interviewer UI label | What happens |
|---|---|---|
| Waiting | Ready to scan | Frames stream but are not buffered for analysis |
| Buffering | Scanning... | Operator triggered scan; frames accumulate to model count. The UI intentionally doesn't show a live frame count here — decoder-restart/keyframe-resync latency means a frame counted at any given instant can reflect video captured several real seconds earlier, so a literal "X/Y" counter implied a precision the pipeline can't guarantee |
| Complete | Live / Fake / Inconclusive / Error | Verdict stored, SSE update, webhook fired. Inconclusive appears when fast-check succeeds over HTTP but returns no verdict; Error appears if the pipeline throws before a verdict is produced at all |
| Stopped | Timed out / Camera off — scan paused / Left the meeting / Stopped | Frame collection ended before a verdict—see reasons below |
Frame collection can end early, before reaching the model's required frame count:
| Reason | Trigger | Recovery |
|---|---|---|
camera-off | Participant's camera turns off mid-scan | Camera must come back on; interviewer starts a new scan |
timeout | Stuck accumulating past the timeout (dynamic — scales with the model's frame count and whether face/background gating is enabled; 30s for a 10-frame scan with no gates, more for larger models or with gating on) | Interviewer starts a new scan, or investigates a stuck quality gate |
manual | Your backend called POST .../stop/:participantId, or the interviewer clicked Stop | Interviewer or backend starts a new scan |
left | Participant left the meeting mid-scan | They must rejoin; whether scanning them again after a rejoin is a new participant ID or a reused one depends on the meeting platform—see Participant Leaves the Meeting |
See participant-stage SSE event for how this surfaces to your integration.
A completed result stays available after a participant leaves
Leaving the meeting does not delete a participant's stored verdict. GET /api/sessions/:id and the interviewer view keep showing their name and score after they've left—only participants still mid-scan (Buffering) when they leave are affected, and only their in-progress collection is discarded.
Frames required before analysis is the number embedded in the model alias (30, 60, or 120 for most families; also 150 for gnn-meet-150-v4_1) — see Overview — Models for the full current list. V2 aliases and any 10 or 90-frame variant don't exist in the model registry and are rejected with a 400; V3.1 aliases still work but aren't offered here.
Frames are extracted from the participant's H.264 stream with FFmpeg and sent as consecutive PNG frames at native capture resolution (no downscale, no frame skipping) to the Moveris API.
On completion: verdict stored (if DB enabled), SSE update to interviewer UI, and verification.completed webhook to your backend.
Camera Toggle and Re-scan¶
flowchart LR
A[webcam_off] --> B{Off longer than threshold?}
B -->|No| C[Ignore brief glitch]
B -->|Yes| D[Camera back on]
D --> E[Participant returns to Ready to scan]
E --> F[Interviewer manually starts a new scan]
F --> G[New verdict + new webhook] Manual trigger still required
Crossing the threshold does not auto-collect frames—it only returns a completed participant to Ready to scan. Someone (interviewer, or your backend via POST .../scan/:participantId) still has to start the new scan, same as any other participant.
| Setting | Default | Description |
|---|---|---|
| Camera-off threshold | ~10 seconds | Minimum time the camera must stay off, after a completed scan, before the participant returns to Ready to scan |
Participant Leaves the Meeting¶
In plain terms
Leaving doesn't erase anything. A finished verdict stays visible. Whether rejoining looks like the same person or a brand-new one depends on the meeting platform and whether the participant is signed in.
| Situation | What happens |
|---|---|
| Leaves mid-scan | That scan stops immediately (participant-stage stopped, reason: "left"). No verdict, no webhook for that attempt. |
| Leaves after a completed verdict | The verdict is untouched—still returned by GET /api/sessions/:id and still shown in the interviewer view. |
| Rejoins, new participant ID | The meeting provider assigns a new id. The bot treats this as a different person: a new Waiting state, a new deterministic session_id if scanned again, and no automatic link to their previous participant ID or verdict. |
| Rejoins, same participant ID | Some meeting platforms reuse the same id for a signed-in participant who leaves and rejoins. The bot recognizes this and treats a later scan as a retry: a fresh random session_id (never a cached result from the earlier scan), linked back via parent_session_id — see Correlating Webhook Results below. |
Rejoin behavior isn't guaranteed either way
Whether a rejoin keeps or changes the participant ID depends on the meeting platform, the client used, and whether the participant is signed in — this isn't something your integration can rely on in advance. If you need to track a person across a meeting regardless of which case applies, correlate on your side (e.g. by matched name, if you're using targeted scanning) rather than assuming either behavior.
Correlating Webhook Results¶
This formula applies to a participant's first scan only — see the warning below for retries and reused-id rejoins.
flowchart LR
subgraph Inputs
M["sessionId<br/>(POST /api/sessions)"]
P["participant_id<br/>(webhook)"]
end
subgraph Output
W["webhook session_id<br/>(UUID v5)"]
end
M --> V["uuid_v5(namespace,<br/>sessionId-participantId)"]
P --> V
V --> W Fixed namespace: 5e8cf7e0-e4c4-4b3a-8d3a-2f1c3b4a5e6f
Idempotent handling
The same participant may trigger multiple webhooks after camera toggles. Store the latest verdict per participant per meeting sessionId, or key on session_id + timestamp.
Retries and reused-id rejoins break this formula on purpose
A retry — manual, an automatic re-scan (camera toggle), or a scan after a participant rejoins with a reused platform id — gets a fresh random session_id instead of the deterministic one, so a re-scan never returns a cached result from an earlier scan of the same participant. The webhook's data.tenant_metadata includes a parent_session_id in this case, pointing back to the original scan's session_id. To reliably match a webhook to a participant in every case, call GET /api/sessions/<sessionId> and match results[participantId].moverisSessionId to the webhook's session_id instead of precomputing it.
Example (Python)¶
import uuid
NAMESPACE = uuid.UUID("5e8cf7e0-e4c4-4b3a-8d3a-2f1c3b4a5e6f")
def moveris_session_id(meeting_session_id: str, participant_id: str) -> str:
name = f"{meeting_session_id}-{participant_id}"
return str(uuid.uuid5(NAMESPACE, name))
Interview Token Lifecycle¶
flowchart TB
C[POST /api/sessions] --> T[Sign interviewToken]
T --> U[Share /interview/token URL]
U --> V[GET /api/interview/token]
V --> E[SSE /api/interview/token/events]
T --> X{24h elapsed?}
X -->|Yes| R[Token rejected] | Property | Value |
|---|---|
| TTL | 24 hours from creation |
| Auth | Token only — no bot API key or Google login |
| Scope | Read-only access to one session's participant results |
Ending a Session¶
flowchart TB
A[Natural end] --> B[call_ended → done]
C[DELETE /api/sessions/:id] --> D[Bot stopped via meeting provider → status: stopped]
E[fatal error] --> F[Session unusable] Related¶
- How It Works — Full architecture and notification channels
- Integration Guide — Webhook payload fields
- API Reference — Session endpoints