Overview
MechZie audio calls use 100ms for the actual audio room and a custom FCM + Redis signaling layer for ringing. 100ms has no concept of “ringing” — it only knows join/leave room. The signaling layer bridges that gap.Who can call whom? Either party (customer or mechanic) can initiate a call on a job once it is in
accepted, en_route, arrived, in_progress, completed, or disputed status.Architecture
Call Flow — Step by Step
1. Caller initiates
201:
hms_room_id + auth_token to join the 100ms room and wait. The other party’s phone rings via FCM.
2. Receiver’s FCM payload
The receiver’s Flutter background handler receives:3. Receiver accepts
200:
call:accepted Socket.io event. Both parties are now in the 100ms room.
4. Receiver declines
200:
call:declined on user:{callerId} Socket.io room.
5. Caller cancels (before answer)
200:
6. Either party ends the call (hangup)
Call this fromleaveCall() in both Flutter apps whenever a party hangs up:
200:
call:ended Socket.io event so each app can return to its idle UI. Idempotent — safe to call multiple times (no-ops if call already ended).
Always call
/call/end from your leaveCall() handler regardless of who initiates the hangup. If both parties call it simultaneously, only the first clears Redis; the second is a silent no-op.7. Missed call (timeout)
If the receiver doesn’t answer within 30 seconds, a BullMQ worker fires:- Deletes the ringing state from Redis
- Emits
call:missedto the caller onuser:{callerId} - Writes a
call_missedaudit entry tojob_messages
Socket.io Events (Call Signaling)
Listen on your connected Socket.io instance. All events are delivered to theuser:{userId} room (auto-joined on connect).
call:accepted
call:declined
call:missed
call:ended
Fired to both parties when either one calls /call/end. Use this to return your UI to idle when the remote party hangs up first.
call:cancelled (FCM only)
Delivered as a data-only FCM message (not a Socket.io event) so it wakes the app even when killed:
Flutter Integration Example
Error Codes
Redis State Machine
The 35 s TTL on
call:ringing is a safety net — the BullMQ timeout fires at 30 s and cleans up Redis itself. The extra 5 s prevents a race where Redis expiry fires before the worker.
The 4 h TTL on call:active is a safety net only — /call/end clears it immediately on hangup. The TTL prevents the key from persisting indefinitely if both clients crash without calling the endpoint.