Taking Calls on the Phone
The app registers as a calling endpoint and answers calls itself. It is the same
endpoint the browser softphone uses: the backend issues SIP credentials at
POST /calls/agents/me/endpoint, and both clients log in with them.
On a phone there is no DOM for the provider's browser SDK to touch, so the app
drives the SIP stack directly and lets react-native-webrtc supply the peer
connection.
How a call reaches the app
Three layers carry the same offer, because each one fails differently.
| layer | reaches | fails when |
|---|---|---|
websocket (calls.agent.{id}) |
sub-second, caller and handoff reason on screen | the app is not open and connected |
| push notification | a backgrounded or killed app | delivery is best-effort; needs FCM credentials |
the carrier ringing fallback_number |
the handset, always | never — it does not depend on our software |
The last one is the floor, and the reason the first two are allowed to be unreliable. An agent is reachable whether or not any of this works, because a phone number rings whether the app is open, backgrounded or killed.
Answering
Answer appears only when a SIP session is actually present — not merely when the device is registered. An Answer button with nothing behind it does nothing and reads as a broken app.
Once connected the screen becomes the call: a running duration, mute, and hang up. Mute disables the outgoing audio track directly and reports muted only when it actually changed one.
Staying in the routing set
The router drops an endpoint that has not re-asserted itself within
agent_registration_tolerance_seconds (90 by default). The app re-registers its
SIP session every 60 seconds and reports registration every 30, the same cadence
the browser softphone has always used.
This matters more than it sounds. An app that refreshes more slowly than the tolerance window is excluded from routing for part of every cycle — the call is never dialled to it at all, there is no invite, and the only symptom is that some calls ring the app and some do not, with nothing to tell them apart.
Availability is not the same as reachability
Two separate controls, deliberately:
- Presence — whether the agent takes calls at all. One value for the person, shared by every device they own. Going offline in the browser takes the person offline everywhere, which is correct and is what the control means.
- Which devices ring — the browser's registration, the app's registration, and the agent's own number, each independent. Silencing the handset does not take the person offline, and signing out of the app does not disturb a colleague's laptop.
When Calls is switched off for a tenant
The app hides the tab, blocks the screen, does not subscribe to the offer channel, and does not register a softphone. The broadcast channel also refuses the subscription server-side.
Module routes are registered globally once a module is active in the central catalog, so a tenant's switch does not currently stop the
/calls/*endpoints from answering. That is a platform-level gap affecting every module, not a Calls one.
What is not built
- Outbound calling from the app. The softphone answers; it does not dial.
- A ring while the app is killed. The push wakes the app, but a killed app
holds no SIP registration, so the carrier leg to
fallback_numberis what actually reaches the agent.