Outbound calling
Place an outbound phone call with SignalWire and choose what happens when the destination answers. Start by calling your own phone and playing a short announcement, then track the call’s progress, run an AI agent, or let users call from your web app.
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 for the Calling API.
- If calling a phone number, a voice-capable phone number purchased in your Space or a verified caller ID.
- If calling from the browser, you need a Subscriber token or guest token to provide to the Browser SDK client.
- A destination device you can answer.
A trial project can dial only numbers it has purchased or verified, and cannot call internationally at all. Any other project can dial any number, but needs international dialing enabled before it reaches another country.
Make your first call
Call a phone you can answer and play a short announcement.
Choose how to place your call
Choose the approach that fits how you want to control the call.
Each approach places the same call, but they differ in how you follow and control it afterward.
SWML doesn’t place calls. It’s the script the call runs once it connects, so you place the call
with REST or Relay and pass SWML in the swml field.
Choose a destination
A call can reach a phone, a SIP destination, a Subscriber, or another Resource in your SignalWire Space.
For this walkthrough, choose a device you can answer and replace
<YOUR_DESTINATION> with its address.
Place the call
Use the REST Calling API with any server-side HTTP client or a SignalWire Server SDK. For WebSocket calling, use a Server SDK or the Browser SDK.
REST
WebSocket (Relay)
The request includes a SignalWire Markup Language (SWML) document in swml. SignalWire runs
it when the destination answers, playing the announcement below.
TypeScript dial() signature depends on the SDK version
The TypeScript samples on this page pin @signalwire/sdk@2.0.5, whose dial() takes a single
options object. The TypeScript dial reference documents the positional
dial(from, to, options) form of a newer release. Match the form to the version you install.
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.
Track the call’s progress
Use callbacks with REST or event handlers with Relay to follow what happens after you dial. Follow the section for the approach you used for your first call.
Track call progress via REST
Add status_url and status_events to your first request to receive call progress callbacks.
Keep the inline swml announcement. The examples below place another call with notifications
for ringing, answered, and ended.
Before running the request, replace <YOUR_STATUS_WEBHOOK_URL> with a webhook endpoint
you control that SignalWire can reach. Your endpoint receives HTTP POST requests as the call
reaches the selected states. See the webhooks guide for endpoint setup and local testing.
status_events accepts created, ringing, answered, and ended; if omitted, it defaults
to ended.
This flow shows a call with ringing, answered, and ended status events enabled:
Track call progress via WebSocket
Relay exposes call state events through call.on(). Because dial() returns after the destination
answers, the handler observes later states such as ending and ended.
The Browser SDK returns a WebRTC Call while it is ringing. Subscribe to call.status$ to follow
it through connecting, connected, and the final states.
For more Relay event handlers, see Event listeners in the Relay client guide.
For Relay, this flow shows the commands your code sends and the events SignalWire returns over the same persistent connection:
Examples
Run an AI agent
Start an AI agent that welcomes the person and answers basic questions about SignalWire.
Before dialing, follow consent, do-not-call, and calling-hour requirements for artificial voices. See the TCPA guide and AI best practices.
Run an AI agent via REST
Send a dial request with an inline SWML ai instruction that starts when the call is answered.
Run an AI agent via WebSocket (Relay)
Dial with Relay, start the agent with call.ai(), and keep the connection open until the call ends.
Leave a voicemail
Use answering machine detection (AMD) to speak to a person immediately or leave a message after a voicemail greeting and beep.
Follow the consent and calling-hour requirements in the TCPA guide.
Leave a voicemail via REST
Use detect_machine and switch to choose the live or voicemail message.
Leave a voicemail via WebSocket (Relay)
Use call.detect() to play the live message after HUMAN or UNKNOWN, or wait for READY after a
machine greeting.
Whisper before connecting two people
Play a private message to <YOUR_AGENT_DESTINATION>, then connect that call to
<YOUR_DESTINATION>.
Play a whisper via REST
Use connect.confirm to play the whisper to the agent before bridging the calls.
Play a whisper via WebSocket (Relay)
Dial the agent first, play the whisper, then connect the caller after playback finishes.
Record the call
Record both sides of an outbound call and retrieve the finished recording URL.
Confirm which parties must consent and announce the recording when required.
Record the call via REST
Start record_call in the background and receive the result at status_url.
Record the call via WebSocket (Relay)
Start call.record() and read the recording URL from its finished event.
Stream the call audio
Stream both sides of a live call to your secure WebSocket endpoint for real-time processing.
Stream call audio via REST
Start stream in the background and send status events to your webhook.
Stream call audio via WebSocket (Relay)
Start call.stream() over the Relay connection and keep it running until the call ends.
Call from the browser
Place a WebRTC call from a web page using a restricted guest token from your backend.
Serve the page over HTTPS or localhost so the browser can access the microphone.
Choose audio, video, or both
client.dial() takes a DialOptions object whose audio and video keys set what
the browser captures and sends. Omit them and the call sends audio only.
Audio + video
Audio only
Video, microphone off
Receive only
A standard video call. Both tracks come from the selected microphone and camera.
A destination address can carry a ?channel=audio or ?channel=video hint that sets the matching
defaults, but options passed to dial() always win. If you’re picking destinations from the
directory, an Address exposes defaultChannel, a ready-to-dial URI, so
you don’t assemble the string yourself. To pin the microphone, camera, or speaker across
every call instead of constraining each dial(), use the device management APIs.
Attach the media to the page
The call exposes localStream$ (what the user sends) and remoteStream$ (what the user receives).
Bind each to a media element’s srcObject. The sample above binds remoteStream$ to an <audio>
element; a video call binds both streams to <video> elements:
Give the local element muted so the user doesn’t hear their own voice back, leave the remote
element unmuted, and give both playsinline for mobile Safari. An audio-only call uses
remoteStream$ the same way.
Watch the browser console as you dial: the call moves through connecting to connected, and
through disconnecting, disconnected, and destroyed once it ends. If dial() rejects with
CallCreateError, the token’s scope doesn’t reach the destination — re-check the
token’s allowed_addresses and its project.
call.hangup() ends the call for everyone. To leave the page but keep the call alive on the platform,
use transfer() instead.
For receiving calls in the browser, see the inbound calls guide; for mute, hold, and other in-call controls, see call controls.