Skip to content

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_endeddone 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 does not show a live X/Y counter. A count would imply the batch matches that exact moment, which the pipeline cannot 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; scanning them again after a rejoin is a new participant ID—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.

Model family Frames required before analysis
mixed-10-* 10
mixed-30-* 30
mixed-60-* 60
mixed-90-* 90
mixed-120-v2, mixed-120-v3_1_1 120

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. Rejoining, though, looks like a brand-new person to the bot—there's no link back to who they were before.

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 the meeting The meeting provider assigns a new participant ID. The bot treats this as a different person: a new Waiting state, a new session_id if scanned again, and no automatic link to their previous participant ID or verdict.

Rejoin breaks participant-ID-based correlation

If your integration tracks a person across a meeting by participant ID, a rejoin will look like someone new joined. There's no reliable way to link the two participant IDs back to the same person from the bot's data alone—if you need that, correlate on your side (e.g. by matched name, if you're using targeted scanning).


Correlating Webhook Results

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.

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]