Skip to navigation

Make and receive calls

View as MarkdownOpen in Claude

Place calls to phones and answer calls to your SignalWire number with SWML, Relay, or the Browser SDK. Each section below takes one of them from a first test call on your own phone through tracking the call’s progress.

Choose an approach

Each approach has its own code and its own kind of Resource on your phone number, so pick one and follow only its section. The sections don’t build on each other, and code from one doesn’t carry over to another.

  • SWML is a document of call instructions that SignalWire runs. You build it with the Server SDKs and send it in a REST request to place a call. To answer one, serve it from your server or host it in SignalWire, which needs none of your code running.
  • WebSocket (Relay) is a Relay client in the Server SDKs. It holds a WebSocket open from your server, receives calls and events as they happen, and controls each call from your code.
  • The Browser SDK puts a person on the call from a web page, with their microphone, camera, and in-call controls.

Prepare for your first call

Have these values ready:

  • Your Space URL, such as <YOUR_SPACE>.signalwire.com.
  • Your Project ID and API token from the Dashboard’s API credentials page. Enable the token’s Voice permission, and Numbers if you assign a phone number in code.
  • A voice-capable phone number in your Space, and a phone you can call it from and answer.
  • For the Browser SDK, a web page you can serve over HTTPS or localhost. The guide creates the Subscriber and its token for you.
Trial projects only call and receive calls from verified numbers

A trial project can’t call international numbers, and it only places calls to and receives calls from numbers you’ve purchased or verified. To test with your own phone, verify it: in the Dashboard, open Phone Numbers, then the Verified tab, select + New, and follow Verified caller ID. To remove the trial restrictions, add a payment method and fund the account with at least $5, as Trial mode describes. Outside a trial, enable international dialing before you call another country.

Replace these values in the samples you run:

ValueReplace with
<YOUR_SPACE>Your Space’s subdomain in <YOUR_SPACE>.signalwire.com
<YOUR_PROJECT_ID>Your Project ID
<YOUR_API_TOKEN>Your API token, used only in server code
<YOUR_CALLER_ID>Your SignalWire phone number, or a verified caller ID, in E.164 format
<YOUR_DESTINATION>Where to call: a phone number in E.164 format such as +12025550123, a SIP URI such as sip:support@example.com, or a Resource Address such as /private/support-rep
<YOUR_PHONE_NUMBER_ID>Your phone number’s ID from its Dashboard page or List phone numbers, when you assign it in code
<YOUR_BASIC_AUTH_PASSWORD>A password you choose for your SWML server
<YOUR_PUBLIC_HOST>The public hostname of your SWML server or tunnel
<YOUR_STATUS_WEBHOOK_URL>An HTTPS endpoint you control that receives call progress
<YOUR_SUBSCRIBER_EMAIL>An email address to identify your test Subscriber
<YOUR_SUBSCRIBER_ACCESS_TOKEN>The Subscriber token from Get a Subscriber token

How an incoming call reaches your code

Placing a call only takes a request from your code. Answering one takes a little setup first, because SignalWire has to know where to send the call. Every phone number has one call handler: a Resource you assign under the number’s Inbound Call Settings. When someone dials the number, SignalWire hands the call to that Resource, and the Resource type decides what happens next:

ApproachResource on the numberWhat happens when a call arrives
SWMLSWML ScriptSignalWire runs the script’s document, first fetching it from your server when the script points at a URL
WebSocket (Relay)Relay ApplicationSignalWire hands the call to the Relay client connected on the application’s topic
Browser SDKSubscriberSignalWire rings the Subscriber’s signed-in clients, such as your web page

So each answering walkthrough below has the same three parts: build the handler, assign it to your number, and call the number to test it. If the number has no call handler, or still points at a different Resource, the call never reaches your code.

Make and receive calls via SWML

A SWML document lists what happens on the call, such as playing a message, forwarding the caller, or recording. SignalWire runs it; your code supplies it.

Place a call via SWML

Send a REST dial request with a SWML document in swml. SignalWire runs the document when the destination answers.

1

Place the call

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as outbound_call.py and run: python outbound_call.py
from signalwire import SWMLBuilder, SWMLService
from signalwire.rest import RestClient
client = RestClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
host="<YOUR_SPACE>.signalwire.com",
)
swml = (
SWMLBuilder(SWMLService(name="outbound-call"))
.say("Hello, welcome to SignalWire!")
.build()
)
call = client.calling.dial(
from_="<YOUR_CALLER_ID>",
to="<YOUR_DESTINATION>",
swml=swml,
)
print(call["id"])

dial() signature depends on the SDK version

@signalwire/sdk@2.0.5 takes a single options object. Newer releases take the positional dial(from, to, options) form.

The cURL example sends the dial command to Call commands.

The REST API returns a call id and status queued. This confirms that SignalWire accepted the request; the call hasn’t necessarily rung or been answered yet. Save the id to identify this call.

Response
{
"billing_ms": null,
"charge": 0,
"charge_details": [
{
"charge": 0.004,
"description": "Outbound Voice"
}
],
"created_at": "2024-05-06T12:20:00Z",
"direction": "outbound-api",
"duration": null,
"duration_ms": null,
"from": "+12069708643",
"id": "0e9c80d7-a149-4917-892d-420043709f45",
"parent_id": null,
"source": "realtime_api",
"status": "queued",
"to": "+15550198765",
"type": "relay_pstn_call",
"url": null
}
2

Answer your phone

Answer the destination phone. You hear “Hello, welcome to SignalWire!”, then the call ends.

If the phone never rings, check that <YOUR_CALLER_ID> is a number in your Space or a verified caller ID, and that a trial project has verified the destination.

Answer a call via SWML

When someone calls your number, SignalWire runs the SWML Script assigned to it. The script either fetches the document from your server on each call, or holds the document itself. Hosting it in SignalWire is the quickest way to hear your first call; serving it from your server lets your code build a different document for each caller.

This flow shows a call handled by a script that fetches its document from your server:

A caller dials your number. SignalWire looks up the number's Resource and sends your server the call details: from, to, direction, and call id. Your server returns a SWML document. SignalWire answers the caller and runs your SWML. When the call finishes, the SWML session ends.
1

Create the SWML Script

SignalWire must reach your server over the internet, so this tab has four parts: run the server, give it a public URL, check the URL, then create the script that points at it.

Run either server below. It answers requests on port 3000 at /swml, but only when they carry the username signalwire and the password you set. SignalWire sends those credentials from the URL you give it, so no one else can fetch your call instructions.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as inbound_swml_server.py and run: python inbound_swml_server.py
from signalwire import SWMLBuilder, SWMLService
service = SWMLService(
name="inbound-swml-server",
route="/swml",
port=3000,
basic_auth=("signalwire", "<YOUR_BASIC_AUTH_PASSWORD>"),
)
SWMLBuilder(service).say("Hello, welcome to SignalWire!")
# Serves the document at /swml. Put the credentials in the URL you give
# SignalWire: https://signalwire:<YOUR_BASIC_AUTH_PASSWORD>@<YOUR_PUBLIC_HOST>/swml
service.serve()

The Server SDKs serve the document with SWMLService and add the instructions through its SWML builder.

While you develop, give the server a public HTTPS URL with a tunnel such as ngrok. In a second terminal, run:

ngrok http 3000

ngrok prints an https:// Forwarding URL. Its hostname is your <YOUR_PUBLIC_HOST>. Keep both the server and ngrok running while you test. A new tunnel usually means a new hostname, so update the script’s URL whenever it changes.

Check that the document is reachable before you point a number at it:

curl -u "signalwire:<YOUR_BASIC_AUTH_PASSWORD>" "https://<YOUR_PUBLIC_HOST>/swml"

The response is the SWML document, starting with {"version": "1.0.0". A 401 means the password doesn’t match the one in your server. See webhook security to also verify that requests come from SignalWire.

Then create the script in the Dashboard:

  1. Open My Resources, select + Add, then Script, then SWML Script.
  2. Name it Inbound welcome and leave Used For set to Calling.
  3. Under Handle Calls Using, choose External URL and enter https://signalwire:<YOUR_BASIC_AUTH_PASSWORD>@<YOUR_PUBLIC_HOST>/swml in Primary Script URL.
  4. Select Create and keep the server running.

You can skip the Dashboard steps: Assign the number in code in the next step creates the script and assigns it in one request.

2

Assign the script to your phone number

Open Phone Numbers, select the number, and select Edit Settings. Under Inbound Call Settings, select Assign Resource, choose the Resource, and save. To assign from the Resource instead, open its Addresses & Phone Numbers tab, select + Add, then Phone Number, and pick a number you own.

A phone number's Edit page in the Dashboard showing the Assign Resource button under Inbound Call Settings

Assigning a Resource under a phone number's Inbound Call Settings

For a script served from your server, the Server SDKs create the script and assign it in one request, replacing the number’s current call handler.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as assign_swml_webhook.py and run: python assign_swml_webhook.py
from signalwire.rest import RestClient
client = RestClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
host="<YOUR_SPACE>.signalwire.com",
)
client.phone_numbers.set_swml_webhook(
"<YOUR_PHONE_NUMBER_ID>",
"https://signalwire:<YOUR_BASIC_AUTH_PASSWORD>@<YOUR_PUBLIC_HOST>/swml",
)

The cURL example calls Update phone number.

To attach an existing hosted script by its ID, use Create a phone number address with handler_type: "calling"; this endpoint is in beta and requires the number to have no call handler yet.

To reach the script from a SIP client or another SignalWire app instead, give it a SIP Address or use its Alias.

3

Call your number

Call your SignalWire number from your phone. You hear “Hello, welcome to SignalWire!”, then the call ends.

If it doesn’t work, check the symptom:

  • You hear an error or silence. SignalWire reached your number but couldn’t run the script. For a script served from your server, rerun the curl check with the exact URL in the script, and confirm the server and ngrok are still running.
  • You hear a different greeting, or the number’s old behavior. The number still points at another Resource. Open it under Phone Numbers and check Inbound Call Settings.
  • The call doesn’t connect at all. In a trial project, call from a verified number.

Track a call via SWML

To follow an outbound call, add status_url and status_events to the dial request in Place a call via SWML. SignalWire sends an HTTP POST to status_url as the call reaches each state you list: created, ringing, answered, or ended. Without status_events, you get ended only. The webhooks guide covers endpoint setup and local testing.

call = client.calling.dial(
from_="<YOUR_CALLER_ID>",
to="<YOUR_DESTINATION>",
swml=swml,
status_url="<YOUR_STATUS_WEBHOOK_URL>",
status_events=["ringing", "answered", "ended"],
)
Your code sends a dial request with from, to, and SWML. SignalWire returns the call id with status queued and rings the destination and reports status ringing. When the person answers, SignalWire reports status answered and runs your SWML. When the call finishes, SignalWire reports status ended.

For an inbound call, the request SignalWire sends your server carries the call in its call object: from, to, direction, and call_id (see the webhook payload reference). The same fields are available inside any SWML document as variables. To read the caller’s number back, replace the greeting with this one:

SWMLBuilder(service).say("Thanks for calling from ${call.from}.")

A SWML document can also fetch data mid-call with request and branch on the result with switch. Methods such as record, connect, and stream take their own status_url to report their progress.

Make and receive calls via WebSocket (Relay)

A Relay client runs on your server and holds a WebSocket connection to SignalWire. It receives calls and events on that connection and sends commands back, so it needs no public HTTP endpoint.

Place a call via WebSocket (Relay)

1

Place the call

Run either program below. It dials the destination, plays a message once the call is answered, and hangs up.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as outbound_relay.py and run: python outbound_relay.py
import asyncio
from signalwire.relay import RelayClient
client = RelayClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
contexts=["default"],
)
async def main():
async with client:
call = await client.dial(
devices=[[{
"type": "phone",
"params": {
"from_number": "<YOUR_CALLER_ID>",
"to_number": "<YOUR_DESTINATION>",
"timeout": 30,
},
}]],
)
async def hang_up_after_playback(_event):
if call.state != "ended":
await call.hangup()
await call.play([{
"type": "tts",
"params": {"text": "Hello, welcome to SignalWire!"},
}], on_completed=hang_up_after_playback)
await call.wait_for_ended()
asyncio.run(main())
2

Answer your phone

Answer the destination phone. You hear “Hello, welcome to SignalWire!”, then the call ends.

If the phone never rings, check that <YOUR_CALLER_ID> is a number in your Space or a verified caller ID, and that a trial project has verified the destination.

Answer a call via WebSocket (Relay)

A Relay handler is a program on your server that stays connected to SignalWire, so it needs no public URL. It subscribes to one or more topics, names you choose and list in contexts. A Relay Application is the Resource that ties your phone number to one topic: calls to the number go to whichever Relay client is subscribed to that topic.

1

Run the call handler

Run either handler below and leave it running while you test. It subscribes to the inbound-calling topic, then waits. When a call arrives, it answers, plays the message, and hangs up, and goes back to waiting for the next call.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as inbound_relay.py and run: python inbound_relay.py
from signalwire.relay import RelayClient
client = RelayClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
contexts=["inbound-calling"],
)
@client.on_call
async def handle_call(call):
await call.answer()
async def hang_up_after_playback(_event):
if call.state != "ended":
await call.hangup()
await call.play([{
"type": "tts",
"params": {"text": "Hello, welcome to SignalWire!"},
}], on_completed=hang_up_after_playback)
await call.wait_for_ended()
client.run()

The Server SDKs document every Relay client configuration option.

2

Assign the topic to your phone number

Create a Relay Application for the topic:

  1. Open My Resources, select + Add, then Relay Application.
  2. Name it Inbound welcome and enter inbound-calling as the Topic. It must match the contexts value in your code.
  3. Select Create.

Open Phone Numbers, select the number, and select Edit Settings. Under Inbound Call Settings, select Assign Resource, choose the Resource, and save. To assign from the Resource instead, open its Addresses & Phone Numbers tab, select + Add, then Phone Number, and pick a number you own.

A phone number's Edit page in the Dashboard showing the Assign Resource button under Inbound Call Settings

Assigning a Resource under a phone number's Inbound Call Settings

The Server SDKs create the Relay Application and assign it in one request, replacing the number’s current call handler. They can also create the Relay Application on its own.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as assign_relay_topic.py and run: python assign_relay_topic.py
from signalwire.rest import RestClient
client = RestClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
host="<YOUR_SPACE>.signalwire.com",
)
client.phone_numbers.set_relay_topic("<YOUR_PHONE_NUMBER_ID>", "inbound-calling")

The cURL example calls Update phone number.

To reach the handler from a SIP client or another SignalWire app instead, give the Relay Application a SIP Address or use its Alias.

3

Call your number

Call your SignalWire number from your phone. You hear “Hello, welcome to SignalWire!”, then the call ends.

If it doesn’t work, check the symptom:

  • The handler logs connection errors and keeps retrying. It can’t sign in. Check the Project ID and API token, and that the token has the Voice permission.
  • The call never reaches your handler. Check that the handler is still running, that its contexts value matches the Relay Application’s Topic exactly, and that the number’s Inbound Call Settings point at that Relay Application.
  • The call doesn’t connect at all. In a trial project, call from a verified number.

Track a call via WebSocket (Relay)

Relay reports call state through call.on(). Add a calling.call.state listener to either first-run program, right after it gets the call, to see states such as answered, ending, and ended, with the end_reason once the call finishes. An outbound dial() returns after the destination answers, so there the listener sees ending and ended.

from signalwire.relay.event import CallStateEvent
def handle_state(event: CallStateEvent):
print(f"State: {event.call_state}, reason: {event.end_reason}")
call.on("calling.call.state", handle_state)

On an inbound call, the Call object also carries the caller’s number in device.params.from_number, with direction and call_id.

This flow shows every message an outbound call exchanges on the connection. The created, ringing, and answered states arrive before dial() returns, so a listener you add afterward sees only ending and ended:

Your code and SignalWire share one persistent WebSocket. Your code sends calling.dial; SignalWire reports created, ringing, and answered through calling.call.state. Your code sends calling.play; SignalWire reports playing and finished through calling.call.play. Your code sends calling.end; SignalWire reports ending and ended through calling.call.state.

For more handlers, see Event listeners in the Relay client guide.

Make and receive calls in the browser

The Browser SDK signs a web page in as a Subscriber, so a person places and answers calls from the page with their own microphone and speakers. The page authenticates with a Subscriber token from your backend, never your API token.

Get a Subscriber token

A Subscriber is the Resource for one person in your app, identified by a reference such as their email. Run this program on your server once to create a test Subscriber and print a token for it. Paste the token in place of <YOUR_SUBSCRIBER_ACCESS_TOKEN> in the pages below.

# Install: python -m pip install signalwire-sdk==3.4.1
# Save as subscriber_token.py and run: python subscriber_token.py
from signalwire.rest import RestClient
client = RestClient(
project="<YOUR_PROJECT_ID>",
token="<YOUR_API_TOKEN>",
host="<YOUR_SPACE>.signalwire.com",
)
# Create the Subscriber once. Later, run only the token request for a fresh token.
client.fabric.subscribers.create(email="<YOUR_SUBSCRIBER_EMAIL>")
sat = client.fabric.tokens.create_subscriber_token(reference="<YOUR_SUBSCRIBER_EMAIL>")
print(sat["token"])

The cURL requests call Create Subscriber, then Create Subscriber token with the Subscriber’s email as reference.

A token lasts two hours by default, so run the token request again when the page stops signing in. In production, your backend issues a token each time a user signs in, as the authentication guide shows.

Place a call from a browser

1

Run the page

Load this script on a page served over HTTPS or localhost, with the elements listed in its comments. A page that only places calls can use a guest token instead of a Subscriber token. To add the camera, pass video: true; DialOptions lists every media option.

JavaScript — Browser SDK
// Install: npm install @signalwire/js@4.0.0-rc.2 rxjs@7.8.2
// Load as a module script on an HTTPS or localhost page with these elements:
// <audio id="remote-audio" autoplay></audio>
// <button id="call" type="button">Call</button>
// <button id="hangup" type="button" disabled>Hang up</button>
// Use a Subscriber Access Token issued by your backend.
import { SignalWire, StaticCredentialProvider } from "@signalwire/js";
const client = new SignalWire(new StaticCredentialProvider({
token: "<YOUR_SUBSCRIBER_ACCESS_TOKEN>",
}));
const remoteAudio = document.querySelector("#remote-audio");
const callButton = document.querySelector("#call");
const hangupButton = document.querySelector("#hangup");
callButton.onclick = async () => {
callButton.disabled = true;
try {
const call = await client.dial("<YOUR_DESTINATION>", { audio: true, video: false });
call.remoteStream$.subscribe((stream) => (remoteAudio.srcObject = stream));
hangupButton.disabled = false;
hangupButton.onclick = () => {
void call.hangup().catch(console.error);
};
call.status$.subscribe((status) => {
if (status === "disconnected" || status === "failed" || status === "destroyed") {
remoteAudio.srcObject = null;
hangupButton.disabled = true;
callButton.disabled = false;
}
});
} catch (error) {
callButton.disabled = false;
console.error(error);
}
};
2

Call and talk

Select Call, allow microphone access, and answer the destination phone. You hear each other. Select Hang up when you finish.

If dial() rejects with CallCreateError, the token’s scope doesn’t reach the destination.

Answer a call in a browser

Assign the Subscriber to your phone number, and calls to the number ring the page signed in as that Subscriber.

1

Run the page

Use the token from Get a Subscriber token.

Receiving calls in the browser requires a Subscriber token

Guest and embed tokens only place calls. The page must sign in as the Subscriber that receives the call.

Load this script on a page served over HTTPS or localhost, with the elements listed in its comments. The page shows who is calling and lets you answer, decline, or hang up.

JavaScript — Browser SDK
// Install: npm install @signalwire/js@4.0.0-rc.2 rxjs@7.8.2
// Load as a module script on an HTTPS or localhost page with these elements:
// <p id="status">Offline</p>
// <p id="caller"></p>
// <audio id="remote-audio" autoplay></audio>
// <button id="answer" type="button" disabled>Answer</button>
// <button id="decline" type="button" disabled>Decline</button>
// <button id="hangup" type="button" disabled>Hang up</button>
// Use a Subscriber Access Token issued by your backend for the Subscriber
// that the phone number rings.
import { SignalWire, StaticCredentialProvider } from "@signalwire/js";
const client = new SignalWire(new StaticCredentialProvider({
token: "<YOUR_SUBSCRIBER_ACCESS_TOKEN>",
}));
const statusLine = document.querySelector("#status");
const callerLine = document.querySelector("#caller");
const remoteAudio = document.querySelector("#remote-audio");
const answerButton = document.querySelector("#answer");
const declineButton = document.querySelector("#decline");
const hangupButton = document.querySelector("#hangup");
const finalStatuses = new Set(["disconnected", "failed", "destroyed"]);
let currentCall = null;
await client.register();
statusLine.textContent = "Online";
client.session.incomingCalls$.subscribe((calls) => {
const ringing = calls.find((call) => call.status === "ringing");
if (!ringing || ringing === currentCall) return;
currentCall = ringing;
callerLine.textContent = `Incoming call from ${ringing.from}`;
answerButton.disabled = false;
declineButton.disabled = false;
ringing.remoteStream$.subscribe((stream) => (remoteAudio.srcObject = stream));
ringing.status$.subscribe((status) => {
statusLine.textContent = status;
if (status !== "ringing") {
answerButton.disabled = true;
declineButton.disabled = true;
}
if (status === "connected") hangupButton.disabled = false;
if (finalStatuses.has(status)) {
remoteAudio.srcObject = null;
hangupButton.disabled = true;
callerLine.textContent = "";
if (currentCall === ringing) currentCall = null;
}
});
});
answerButton.onclick = () => {
void currentCall?.answer({ audio: true, video: false });
};
declineButton.onclick = () => {
void currentCall?.reject();
};
hangupButton.onclick = () => {
void currentCall?.hangup().catch(console.error);
};

Awaiting register() signs the page in and marks the Subscriber online, so the page shows Online. Keep it open while you test: calls ring only while it’s online.

2

Assign the Subscriber to your phone number

The Subscriber you created is listed under My Resources, so you can pick it like any other Resource.

Open Phone Numbers, select the number, and select Edit Settings. Under Inbound Call Settings, select Assign Resource, choose the Resource, and save. To assign from the Resource instead, open its Addresses & Phone Numbers tab, select + Add, then Phone Number, and pick a number you own.

A phone number's Edit page in the Dashboard showing the Assign Resource button under Inbound Call Settings

Assigning a Resource under a phone number's Inbound Call Settings
3

Call your number and answer

Call your SignalWire number from your phone. The page shows the incoming call. Select Answer, allow microphone access, and select Hang up when you finish.

If it doesn’t work, check the symptom:

  • The page never shows Online. register() failed, most often because the token expired. Issue a new token and reload the page.
  • The page is Online but never rings. Check that the number’s Inbound Call Settings point at the same Subscriber the token was issued for.
  • The call connects but you can’t hear each other. Allow microphone access, and serve the page over HTTPS or localhost.
  • The call doesn’t connect at all. In a trial project, call from a verified number.

The Browser SDK inbound calls guide builds a complete receiver page, including two callers ringing at once.

Track a call in a browser

The Browser SDK is the client-side counterpart of the Relay client: the page holds a WebSocket connection to SignalWire, and call state arrives as events on it rather than as HTTP callbacks. As in Track a call via WebSocket (Relay), you subscribe to a call’s events, here through the call’s status$ observable instead of call.on().

An outbound dial() returns the call while it is ringing. The call then moves through connecting and connected, and through disconnecting, disconnected, and destroyed once it ends. With disconnected, failed, and destroyed treated as final, one subscription updates the page for every outcome. To show the status on the calling page, add <p id="status">Idle</p> to Place a call from a browser, then add the lookup below and replace that page’s status$ subscription:

JavaScript — Browser SDK
const statusLine = document.querySelector("#status");
// Inside callButton.onclick, after dial() resolves:
call.status$.subscribe((status) => {
statusLine.textContent = `Call: ${status}`;
if (status === "disconnected" || status === "failed" || status === "destroyed") {
remoteAudio.srcObject = null;
hangupButton.disabled = true;
callButton.disabled = false;
}
});

The answering page in Answer a call in a browser already writes each status to #status. An inbound call follows the same path once it’s answered; one that leaves ringing without reaching connected was declined, or the caller hung up.

Each entry in incomingCalls$ is a Call with direction set to inbound, the browser’s equivalent of the Relay Call a server handler receives. Read from for the caller’s number or Address, to for the number or Address they dialed, and fromName for a display name. SignalWire sends _undef_ as fromName when the caller supplied none, so fall back to from.

Next steps