Skip to navigation

Call legs

Why one call shows up as several records
View as MarkdownOpen in Claude

A forwarded call is one conversation to the people on it. To SignalWire it is two legs: the caller’s connection to your number, and the connection SignalWire dials to the destination. Each leg has its own Call ID, its own log record, its own status callbacks, and its own charge. Once you can see the legs in a call, the second log entry, the extra callback, and the unfamiliar ID in a webhook all make sense.

This page explains what a leg is, how legs come into being, how to tell which leg you’re looking at, and what you can do to one leg without touching the other.

What a call leg is

A call leg is the connection between two user agents. A user agent is anything that can hold up its end of a call: a phone on the public network, a SIP device or PBX, a browser running the Browser SDK, or SignalWire itself. A call is everything the person on the other end experiences, from the moment it is placed to the moment it ends, and it is made of one or more legs.

The leg that started the call is the A-leg. A leg SignalWire dials on its behalf is a B-leg. Forward an inbound call to one SIP endpoint and the call has two legs. Ring four endpoints at once and it has five, because every destination SignalWire rings is a leg, including the ones that never answer.

A caller is connected to SignalWire by the A-leg, and SignalWire is connected to the destination by the B-leg. The A-leg is inbound and has its own Call ID. The B-leg is outbound, has its own Call ID, and names the A-leg as its parent. Once bridged, each leg's peer is the other. Each leg is its own log record and its own charge.

The Browser SDK also uses the word leg for the connections inside one browser call: the main leg, the screen-share leg, and the additional-device leg. Each of those auxiliary legs opens its own connection to SignalWire with its own call ID.

How legs are created

Every leg starts the same way: someone, or something, dials. The table lists what adds legs to a call and how many.

What happensLegsWhat to expect
A caller dials your number1The A-leg. Your SWML document, Relay handler, cXML, or call flow runs on it.
Your code dials out over the REST Calling API or Relay1SignalWire places the leg and gives you its ID. It becomes the A-leg of whatever you connect it to next.
You connect the call to one destination with SWML connect, Relay connect from the Server SDKs, cXML <Dial>, or the Call Flow Builder Forward to Phone node+1A B-leg to the destination. When it answers, SignalWire bridges the two legs so the parties hear each other.
You dial several destinations in parallel+1 per destinationEach destination is its own leg. The first to answer is bridged, and the others end.
You dial a Subscriber+1 per registered device or endpointEvery phone, browser, and SIP endpoint registered to the Subscriber rings as its own leg. The leg that answers wins.
You connect a queued caller to an agent0The waiting caller is one leg and the agent’s call is another, placed when the agent dialed in or your code dialed out. A connect to the queue bridges those two existing legs.
Participants join a conference or video room0Each participant joins the room on the leg they already have, whether they arrived by phone, SIP, or browser.
You transfer a SIP call with SIP REFER0The SIP device places the new call itself, and your app hangs up SignalWire’s leg once the transfer succeeds. See how SIP REFER works.
You move a leg to a new SWML document with transfer0The same leg continues under new instructions. Its Call ID and variables carry over.

Forward calls walks through the two-leg case with code, and Make and receive calls covers the single-leg start.

What each leg keeps

Each leg carries its own state: a direction, a from and to, a call state with its own start, answer, and end times, and its own log record. On a B-leg, from is the caller ID you chose and to is the destination, so the same call looks different depending on which leg reports it.

In SWML, each leg has its own call object and its own variables.

Variables belong to one leg

SWML variables live on the leg that set them, for as long as that leg lasts. A document that runs on the leg connect dialed, such as a confirm script, starts with an empty vars and its own call object. On the leg that issued connect, every variable survives, and connect_result is set when the document resumes. For how legs relate, see Call legs.

The AI agent’s global_data follows the same rule. It persists across AI sessions on one leg through the ai_agents_global_data variable, as the AI sidecar reference describes, and it does not cross to another leg.

Tell legs apart

Every surface identifies legs with the same handful of fields under slightly different names. The glossary lists all of them side by side. The ones below relate legs to each other.

A Call ID names one leg for its whole life, and every leg in a call has a different one. A Segment ID names the leg’s log record, and it is the value to quote to Support. A leg’s first segment reuses its Call ID, so the two match for most calls. When SignalWire re-routes a leg to a new handler, a new segment starts on the same leg: the Segment ID changes and the Call ID does not. That happens when a SIP device sends SignalWire a REFER, and when cXML transfers the call. The Compatibility API calls the Segment ID CallSid, and the Segment IDs page shows where to find it in the Dashboard.

The parent is the leg that created this one. SWML webhooks and status callbacks send it as parent, the REST voice logs and Calling API as parent_id, and the Compatibility API as ParentCallSid. The peer is the leg this one is connected to. It arrives as peer in connect status callbacks and in Relay connect events. A dialed leg has its peer from the moment it’s created, so a confirm script can read it. SignalWire clears it when the bridge ends, so a SWML document that resumes after connect has no call.peer.

Two more fields say which leg won and which legs belong together. The dial_winner flag marks the leg that answered a Relay or Calling API dial, including the winning device of a parallel dial. Legs that a connect dials don’t carry it. A tag is a label you set on a Relay dial or connect request. Every leg that request creates shares it, and SignalWire echoes it on those legs’ own events and as peer.tag in the connected event of the leg that issued the connect.

Here is the call object a confirm script receives on the B-leg of a connect, abridged to the fields above. The leg is outbound, its segment_id still matches its call_id, and both its parent and its peer are the A-leg:

{
"call_id": "c3d4e5f6-2222-3333-4444-555566667777",
"segment_id": "c3d4e5f6-2222-3333-4444-555566667777",
"call_state": "answered",
"direction": "outbound",
"type": "phone",
"from": "+15551230002",
"to": "+15551230003",
"parent": {
"call_id": "a1b2c3d4-1111-2222-3333-444455556666"
},
"peer": {
"call_id": "a1b2c3d4-1111-2222-3333-444455556666"
}
}

The full field list is in the inbound call webhook reference.

Where legs show up

SurfaceHow legs appear
Dashboard logsEach leg is its own entry under Logs, and the detail view shows its Segment ID.
REST voice logsThe voice logs return one entry per leg. A B-leg’s parent_id points at the leg that created it, which is how you reassemble a call from its legs.
Compatibility APIList all Calls accepts a ParentCallSid filter that returns the legs a given leg created, and a <Dial> sends the new leg’s DialCallSid to its action URL.
Status callbacksEach leg reports its own state changes to its own callback URL. connect callbacks include the peer from the moment the legs are bridged, including the disconnected callback. Webhooks explains the delivery rules.
RecordingsA recording belongs to a leg. The Recordings API names the leg in a field for its type, such as relay_pstn_leg_id, and that value is not the leg’s Call ID or Segment ID. A stereo recording puts the two sides of a bridged call on separate channels, as the record calls guide describes.

Act on one leg or both

Because the legs are separate, what happens when one ends depends on which one it is. When the dialed leg hangs up, the leg that connected it carries on: a SWML document continues past its connect, and by default a Relay handler receives the disconnected event. When the leg that issued connect hangs up, SignalWire hangs up the dialed leg too, because nothing else controls it. A connect to a queue is the exception. Once the agent and the caller are bridged, the agent’s document doesn’t resume, and execute_after_queue decides what the agent’s leg does next.

To break the bridge and keep this leg, send the REST Calling API’s calling.disconnect command or the Relay disconnect method to the leg that issued connect. Your code can then play a message or connect the caller somewhere else. The other leg doesn’t hang up: it stays up and parked, and no event fires for it, so hang it up or connect it yourself.

Two operations change a call without adding a leg. SIP REFER asks the SIP device to place the next call itself. When it succeeds, your app hangs up SignalWire’s leg and the call continues without SignalWire. SWML transfer moves the same leg to a new document, and nothing about the leg changes except what runs on it.

Legs and billing

Each leg is billed on its own, and each voice log entry carries its own charge and charge_details. A forwarded call therefore produces two entries: the caller’s inbound leg to your number, and the outbound leg SignalWire dialed to the destination. A parallel dial produces an entry for every destination SignalWire dialed, including the ones that never answered.

Next steps