Learn

Resources

Use Cases

IVR and Call Routing

— IVR and Call Routing

IVR and call routing where every caller lands in the right place.

Define the call flow in your code and route on live caller data to a queue, an agent, or an AI. The platform owns the call's state so your app never rebuilds context on a transfer.

SW-UseCase-IVRCallRouting

The hard part

Routing breaks when the call can't hold its own state.

Traditional platforms treat a call as a one-off transaction. The moment you route it, the caller's identity, their authentication, and everything they've told you disappears.

So your app becomes the memory, rebuilding call state from webhook events that arrive late, out of order, or not at all, always a step behind a call moving in real time. On SignalWire’s control plane, the call carries its own state. Identity, authentication, and context stay with it through every handoff, to a human, a queue, or an AI. Route it as many times as you want; nothing gets rebuilt and no one starts over.

How it works

Follow one call’s state through every handoff

The call arrives as a single stateful object and carries everything it learns. Click any step to inspect it.

01

The call arrives as an object

Before you even answer, the call is already a stateful object with its own ID. The call arrives whole and you can address it at any point while it's live.

[inbound → call_id: b5f2c3d4-... (state: created)]

02

State builds as the call continues

Authentication, account details, and menu choices attach to the call itself and travel with it.

[set → variables persist on the call]

03

The control plane routes the call

The control plane owns the call and its state, so routing runs on accurate, real-time data instead of reconstructions.

[switch on live state → connect to destination]

04

The call arrives with full context

When the call arrives, whether at a queue, a human, or an AI agent, it brings along its identity, authentication, and context.

[connect → call_id: b5f2c3d4-... keeps its state]

05

The live call is referenced by ID

Because the call is a persistent object, you can reference it by ID and instruct it mid-call: reroute, hand off, or end it.

[execute_rpc on call_id: b5f2c3d4-... → instruct anytime]

01

The call arrives as an object

Before you even answer, the call is already a stateful object with its own ID. The call arrives whole and you can address it at any point while it's live.

[inbound → call_id: b5f2c3d4-... (state: created)]

02

State builds as the call continues

Authentication, account details, and menu choices attach to the call itself and travel with it.

[set → variables persist on the call]

03

The control plane routes the call

The control plane owns the call and its state, so routing runs on accurate, real-time data instead of reconstructions.

[switch on live state → connect to destination]

04

The call arrives with full context

When the call arrives, whether at a queue, a human, or an AI agent, it brings along its identity, authentication, and context.

[connect → call_id: b5f2c3d4-... keeps its state]

05

The live call is referenced by ID

Because the call is a persistent object, you can reference it by ID and instruct it mid-call: reroute, hand off, or end it.

[execute_rpc on call_id: b5f2c3d4-... → instruct anytime]

01

The call arrives as an object

Before you even answer, the call is already a stateful object with its own ID. The call arrives whole and you can address it at any point while it's live.

[inbound → call_id: b5f2c3d4-... (state: created)]

02

State builds as the call continues

Authentication, account details, and menu choices attach to the call itself and travel with it.

[set → variables persist on the call]

03

The control plane routes the call

The control plane owns the call and its state, so routing runs on accurate, real-time data instead of reconstructions.

[switch on live state → connect to destination]

04

The call arrives with full context

When the call arrives, whether at a queue, a human, or an AI agent, it brings along its identity, authentication, and context.

[connect → call_id: b5f2c3d4-... keeps its state]

05

The live call is referenced by ID

Because the call is a persistent object, you can reference it by ID and instruct it mid-call: reroute, hand off, or end it.

[execute_rpc on call_id: b5f2c3d4-... → instruct anytime]

Build it

Route your first call in a few lines

Define the flow in code, route on live data, and hand off to a human or an AI. The control plane owns the call's state so identity and context arrive with the call.

yaml

copy

01

02

03

04

05

06

07

08

09

10

11

12

version: 1.0.0
sections:
  main:
    - answer
    # the caller's number is available inline as %{call.from}
    - request:
        url: "https://api.example.com/lookup?from=%{call.from}"
        method: GET
        save_variables: true          # response JSON becomes call variables under request_response.*
    # branch on the data you just fetched, inside the call flow itself
    - switch:
        variable: request_response.tier
        case:
          vip:
            # known VIP — skip the menu, connect straight to their account manager
            - connect: { to: "%{request_response.account_manager}" }
        default:
          # one verb: play the menu and collect a digit OR speech, with timeouts
          - prompt:
              play: "say:Press 1 for sales, 2 for support, or say what you need."
              max_digits: 1
              speech_hints: ["sales", "support", "billing"]
          # prompt_value holds the pressed digit or the spoken words
          - switch:
              variable: prompt_value
              case:
                "1": [ connect: { to: "+15551112222" } ]
                sales: [ connect: { to: "+15551112222" } ]
                "2": [ connect: { to: "sip:support@example.com" } ]
                support: [ connect: { to: "sip:support@example.com" } ]
              default:
                # unclear input — hand the whole call to an AI agent, same document
                - ai:
                    prompt:
                      text: "You are the front desk. Find out what the caller needs and route them."

yaml

copy

01

02

03

04

05

06

07

08

09

10

11

12

version: 1.0.0
sections:
  main:
    - answer
    # the caller's number is available inline as %{call.from}
    - request:
        url: "https://api.example.com/lookup?from=%{call.from}"
        method: GET
        save_variables: true          # response JSON becomes call variables under request_response.*
    # branch on the data you just fetched, inside the call flow itself
    - switch:
        variable: request_response.tier
        case:
          vip:
            # known VIP — skip the menu, connect straight to their account manager
            - connect: { to: "%{request_response.account_manager}" }
        default:
          # one verb: play the menu and collect a digit OR speech, with timeouts
          - prompt:
              play: "say:Press 1 for sales, 2 for support, or say what you need."
              max_digits: 1
              speech_hints: ["sales", "support", "billing"]
          # prompt_value holds the pressed digit or the spoken words
          - switch:
              variable: prompt_value
              case:
                "1": [ connect: { to: "+15551112222" } ]
                sales: [ connect: { to: "+15551112222" } ]
                "2": [ connect: { to: "sip:support@example.com" } ]
                support: [ connect: { to: "sip:support@example.com" } ]
              default:
                # unclear input — hand the whole call to an AI agent, same document
                - ai:
                    prompt:
                      text: "You are the front desk. Find out what the caller needs and route them."

yaml

copy

01

02

03

04

05

06

07

08

09

10

11

12

version: 1.0.0
sections:
  main:
    - answer
    # the caller's number is available inline as %{call.from}
    - request:
        url: "https://api.example.com/lookup?from=%{call.from}"
        method: GET
        save_variables: true          # response JSON becomes call variables under request_response.*
    # branch on the data you just fetched, inside the call flow itself
    - switch:
        variable: request_response.tier
        case:
          vip:
            # known VIP — skip the menu, connect straight to their account manager
            - connect: { to: "%{request_response.account_manager}" }
        default:
          # one verb: play the menu and collect a digit OR speech, with timeouts
          - prompt:
              play: "say:Press 1 for sales, 2 for support, or say what you need."
              max_digits: 1
              speech_hints: ["sales", "support", "billing"]
          # prompt_value holds the pressed digit or the spoken words
          - switch:
              variable: prompt_value
              case:
                "1": [ connect: { to: "+15551112222" } ]
                sales: [ connect: { to: "+15551112222" } ]
                "2": [ connect: { to: "sip:support@example.com" } ]
                support: [ connect: { to: "sip:support@example.com" } ]
              default:
                # unclear input — hand the whole call to an AI agent, same document
                - ai:
                    prompt:
                      text: "You are the front desk. Find out what the caller needs and route them."

yaml

copy

01

02

03

04

05

06

07

08

09

10

11

12

version: 1.0.0
sections:
  main:
    - answer
    # the caller's number is available inline as %{call.from}
    - request:
        url: "https://api.example.com/lookup?from=%{call.from}"
        method: GET
        save_variables: true          # response JSON becomes call variables under request_response.*
    # branch on the data you just fetched, inside the call flow itself
    - switch:
        variable: request_response.tier
        case:
          vip:
            # known VIP — skip the menu, connect straight to their account manager
            - connect: { to: "%{request_response.account_manager}" }
        default:
          # one verb: play the menu and collect a digit OR speech, with timeouts
          - prompt:
              play: "say:Press 1 for sales, 2 for support, or say what you need."
              max_digits: 1
              speech_hints: ["sales", "support", "billing"]
          # prompt_value holds the pressed digit or the spoken words
          - switch:
              variable: prompt_value
              case:
                "1": [ connect: { to: "+15551112222" } ]
                sales: [ connect: { to: "+15551112222" } ]
                "2": [ connect: { to: "sip:support@example.com" } ]
                support: [ connect: { to: "sip:support@example.com" } ]
              default:
                # unclear input — hand the whole call to an AI agent, same document
                - ai:
                    prompt:
                      text: "You are the front desk. Find out what the caller needs and route them."

What you'll have after the quickstart

One flow that owns the whole call, no separate session store to keep in sync

Handoffs that carry the caller's identity, auth, and context

An AI voice agent as a native routing destination

One flow routes any protocol, no separate code paths

What you'll have after the quickstart

One flow that owns the whole call, no separate session store to keep in sync

Handoffs that carry the caller's identity, auth, and context

An AI voice agent as a native routing destination

One flow routes any protocol, no separate code paths

What you'll have after the quickstart

One flow that owns the whole call, no separate session store to keep in sync

Handoffs that carry the caller's identity, auth, and context

An AI voice agent as a native routing destination

One flow routes any protocol, no separate code paths

The platform behind it

The SignalWire products behind IVR & Call Routing

Define the flow in code, route on live data, and hand off to a human or an AI. The control plane owns the call's state so identity and context arrive with the call.

SWML

Define call flows, routing logic, and menus as declarative markup, JSON or YAML, that the platform runs for you.

Voice APIs

Programmable voice over phone numbers, SIP, and WebRTC, with call control built into the platform.

AI Agents

Add an AI voice agent as a routing destination, scoped and governed by the rules you set.

Queues

Hold and distribute callers with wait handling, so the right agent picks up in the right order.

RELAY

Control any live call in real time over a WebSocket: transfer, reroute, or update it by UUID.

Server SDKs

Build call and agent logic in Python or nine other languages.

SWML

Define call flows, routing logic, and menus as declarative markup, JSON or YAML, that the platform runs for you.

Voice APIs

Programmable voice over phone numbers, SIP, and WebRTC, with call control built into the platform.

AI Agents

Add an AI voice agent as a routing destination, scoped and governed by the rules you set.

Queues

Hold and distribute callers with wait handling, so the right agent picks up in the right order.

RELAY

Control any live call in real time over a WebSocket: transfer, reroute, or update it by UUID.

Server SDKs

Build call and agent logic in Python or nine other languages.

SWML

Define call flows, routing logic, and menus as declarative markup, JSON or YAML, that the platform runs for you.

Voice APIs

Programmable voice over phone numbers, SIP, and WebRTC, with call control built into the platform.

AI Agents

Add an AI voice agent as a routing destination, scoped and governed by the rules you set.

Queues

Hold and distribute callers with wait handling, so the right agent picks up in the right order.

RELAY

Control any live call in real time over a WebSocket: transfer, reroute, or update it by UUID.

Server SDKs

Build call and agent logic in Python or nine other languages.


The hard parts

The hard parts and how SignalWire handles them.

The Challenge

How SignalWire Handles it

Context is lost because every transfer drops the caller's identity, auth, and history.

The call is a stateful object with its own ID. Identity, auth, and context travel with it through every transfer.

You rebuild call state from webhook events in your own database, always a step behind a call moving in real time.

The control plane owns the call's state natively. No session store to maintain, no reconstructing state from event logs.

Telephony, routing, queuing, and AI come from different vendors you stitch together, each a new integration and failure point.

Voice, routing, queues, and AI agents are native to one platform. They compose in a single flow, no integration between them.

Systems that work in the demo collapse under concurrent production load, dropped calls, SIP failures, and slowdowns that only show up at scale.

A carrier-grade engine with auto-scaling and redundancy built in. It holds up at the volume that breaks patched-together platforms.

The Challenge

Context is lost because every transfer drops the caller's identity, auth, and history.

You rebuild call state from webhook events in your own database, always a step behind a call moving in real time.

Telephony, routing, queuing, and AI come from different vendors you stitch together, each a new integration and failure point.

Systems that work in the demo collapse under concurrent production load, dropped calls, SIP failures, and slowdowns that only show up at scale.

How SignalWire Handles it

The call is a stateful object with its own ID. Identity, auth, and context travel with it through every transfer.

The control plane owns the call's state natively. No session store to maintain, no reconstructing state from event logs.

Voice, routing, queues, and AI agents are native to one platform. They compose in a single flow, no integration between them.

A carrier-grade engine with auto-scaling and redundancy built in. It holds up at the volume that breaks patched-together platforms.

In production

Built by teams that route at scale

Angular Gradient Image
  • "Anyone that's looking for a communications platform: SignalWire is a great choice. You'll get great customer service, you'll get great support, you'll get great pricing, you'll get great connectivity."

    Avatar

    Phil Gordon

    CEO / Co-Founder at Prompt.IO

  • "Anyone that's looking for a communications platform: SignalWire is a great choice. You'll get great customer service, you'll get great support, you'll get great pricing, you'll get great connectivity."

    Avatar

    Phil Gordon

    CEO / Co-Founder at Prompt.IO

Why Québec's emergency call center moved from a self-built FreeSWITCH platform to SignalWire as call volume grew.

Angular Gradient Image
  • "Anyone that's looking for a communications platform: SignalWire is a great choice. You'll get great customer service, you'll get great support, you'll get great pricing, you'll get great connectivity."

    Avatar

    Phil Gordon

    CEO / Co-Founder at Prompt.IO

  • "Anyone that's looking for a communications platform: SignalWire is a great choice. You'll get great customer service, you'll get great support, you'll get great pricing, you'll get great connectivity."

    Avatar

    Phil Gordon

    CEO / Co-Founder at Prompt.IO

Why Québec's emergency call center moved from a self-built FreeSWITCH platform to SignalWire as call volume grew.

Angular Gradient Image
  • "Anyone that's looking for a communications platform: SignalWire is a great choice. You'll get great customer service, you'll get great support, you'll get great pricing, you'll get great connectivity."

    Avatar

    Phil Gordon

    CEO / Co-Founder at Prompt.IO

  • "Anyone that's looking for a communications platform: SignalWire is a great choice. You'll get great customer service, you'll get great support, you'll get great pricing, you'll get great connectivity."

    Avatar

    Phil Gordon

    CEO / Co-Founder at Prompt.IO

Why Québec's emergency call center moved from a self-built FreeSWITCH platform to SignalWire as call volume grew.


FAQ

Frequently asked questions

Everything you might want to know about call routing, from migrating off Twilio to handling AI handoffs.

How is this different from Twilio or other CPaaS platforms?

On a CPaaS, the platform fires events and your application holds the call's state, so you rebuild identity, context, and transfer logic yourself. SignalWire owns the call as a stateful object. Identity, authentication, and context stay with the call through every transfer, so you write the routing logic, not the plumbing that keeps state in sync.

Can I migrate from Twilio without rewriting everything?

Yes. SignalWire supports cXML, a compatibility layer that runs TwiML-style scripts, so much of your existing code works by changing the endpoint. You can migrate incrementally and move to SWML for the flows where you want the control plane's state handling.

Can I route calls to an AI agent?

Yes. An AI voice agent is a native routing destination, the same as a queue or a human. You define what the agent can do with your own rules, and the call keeps its full context when it hands off, so the agent starts with everything the caller already gave.

Do I need to write different code for phone, SIP, and WebRTC callers?

No. A call is the same object whatever protocol it arrives on, so one flow handles phone numbers, SIP, and WebRTC callers, and routes them to the same destinations with the same logic.

How is call routing priced?

Routing runs on standard per-minute call pricing, with capabilities like AI, recording, and queuing billed as you use them. There are no per-feature platform minimums, and you can find current rates on the pricing page.

Can I change or reroute a call while it's in progress?

Yes. Every call is addressable by its ID for its whole life, so you can transfer it, reroute it, or update it in real time, without rebuilding the call or losing its state.

Angular Gradient Image

Ready to ship your first call flow?

Sign up and start building. Every SignalWire Space includes free credit to test end to end.

Angular Gradient Image

Ready to ship your first call flow?

Sign up and start building. Every SignalWire Space includes free credit to test end to end.

Angular Gradient Image

Ready to ship your first call flow?

Sign up and start building. Every SignalWire Space includes free credit to test end to end.