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_number is what actually reaches the agent.