To receive calls in a web app, bring a signed-in user online, show a ringing UI when someone calls them, and let them accept or decline. The result is a receiver you can call from any phone, SIP endpoint, or another browser tab.
Before you start. Inbound calls require a Subscriber Access Token (SAT) issued for a specific user. Embed tokens and guest tokens are outbound-only and can’t receive calls.
Subscribe to client.session.incomingCalls$. The stream emits the current list of inbound calls every time it changes, not one event per call — filter by status === "ringing" to find calls that still need a decision.
Each entry is a Call with direction: "inbound". Display the caller from these properties:
The SDK hands you the raw list — it doesn’t queue, dedupe, or pick a call for you. Two simultaneous callers land in the same emission, and calls stay in the array through every status transition (only dropping out when destroyed), so the status === "ringing" filter is what tells you which entries still need a decision.
A ringing call ends one of three ways: the user accepts, the user declines, or the caller gives up. Use answer() to accept and reject() to decline; subscribe to status$ to detect any of the three so the ringing UI tears down from a single place.
Accept the call. answer() takes a MediaOptions object that controls which tracks the user sends back — audio defaults to true, video defaults to false:
Standard video call. Both tracks acquired from the selected mic and camera.
Decline the call. reject() declines before any media negotiates — the caller sees a normal decline; the session never picks up:
Dismiss the ringing UI. Subscribe to the call’s status$ and dismiss on the first emission that isn’t "ringing". That single handler covers all three outcomes — accepted, declined, or caller-gave-up:
After ringing, the call walks connecting → connected → disconnected. React to connected for the in-call UI, and disconnected / destroyed for the final cleanup.
Once the call is connected, attach the local and remote media to <video> elements. The shape is identical to an outbound call — bind the localStream$ and remoteStream$ observables to each element’s srcObject:
The local element needs muted so the user doesn’t echo their own voice; the remote element must not be muted or no one is heard. Both need playsinline for mobile Safari.
Call hangup() when the user clicks the hang-up button or navigates away. The call transitions through disconnecting → disconnected → destroyed; any subscriptions on the call complete naturally.
If the user closes the tab without calling hangup(), the SDK still tears the call down when the page unloads. Calling hangup() explicitly gives you a clean point to dismiss the in-call UI before the connection drops.
To leave the page but keep the call alive on the platform — a transfer-and-disappear flow — use transfer() instead.
The fastest way to verify inbound calls end-to-end is to issue a SAT, load the demo as your user, then place the call yourself with a server-side dial that runs inline SWML. The demo surfaces a ringing UI you can accept with Answer, then the SWML script plays a public test MP4 into the call — you see real audio and video on the receiving side without needing a second browser or a phone.
Pick a reference for the user the call will arrive on — usually an email, but any stable ID works. If a user (Subscriber) with that reference doesn’t exist yet, this endpoint creates one (set first_name, last_name, or password in the same body if you want); otherwise it returns a fresh token for the existing user.
The response token is the SAT you’ll plug into the demo. For the production version of this flow — where your backend issues SATs to clients — see the Authentication guide.
Save the page below as inbound-demo.html and open it over HTTPS (or localhost). Paste the SAT and click Come online.
Once registered, the log prints the user’s dialable /private/<name> address(es) — copy one for the next step. (You can also grab it from the Dashboard Resources page if you prefer the UI.)
Leave the page open — when the call arrives, the Caller line populates and the Answer / Decline buttons enable. After you accept, Hang up enables so you can end the call.
There are several ways to dial the user — another browser tab signed in as a different user, a SIP softphone configured against the user’s SIP endpoint, a PSTN call from a phone, or a server-side dial via the Calling REST API. This guide uses the Calling API so you can verify the flow from a single terminal. The body below carries inline SWML that plays a public test MP4 — click Answer in the demo when the call rings and the platform plays that file into the call so the remote <video> tile shows real audio and video.
Set from to any phone number or SIP credential on your project — server-side dials originate from those. Set to to the address the demo log printed in step 2 (e.g. /private/john-doe). Paste the body below into the request above:
The demo logs the ringing call and enables the Answer / Decline buttons. Click Answer to accept (the demo answers with audio: true, video: true) — the streams attach, the local tile shows your webcam preview, and the remote tile shows whatever the SWML leg sends back. The SWML script hangs up automatically when playback finishes, or click Hang up to end the call from your side.