Call Routing Strategies
The decision tree from "phone rings" to "human answers" has 7 canonical strategies. Each has different math, different agent fairness, and different customer-experience trade-offs.
1. Simple forward
One number forwards to one destination. The simplest case.
- connect:
to: +13105551111
timeout: 30
When to use: single-agent operations, small businesses with one phone line, after-hours forwarding to on-call. Trade-off: zero redundancy — if the destination doesn't answer, the call fails.
2. Ring all (simultaneous)
Ring every agent at once. First to answer wins.
- connect:
to:
- +13105551111
- +13105552222
- +13105553333
timeout: 30
When to use: small teams (3-5 agents), urgent service categories where first-available wins (emergency plumbing, locksmiths). Trade-off: disruptive — every phone rings every call. Agents annoyed. Higher answer rate than hunt group but worse for agent focus.
3. Hunt group (sequential / find-me-follow-me)
Try agent 1, then agent 2, then agent 3 in sequence. Each gets the full ring timeout before falling through.
- connect:
to: +13105551111
timeout: 15
- connect:
to: +13105552222
timeout: 15
- connect:
to: +13105553333
timeout: 15
- play: say:All agents are busy, please leave a message
- record_call: {}
When to use: strict seniority hierarchy (senior agent gets first shot), or after-hours escalation chains (try office → try cell → try backup). Trade-off: unfair — agent 1 takes all the calls, agents 2-3 starve.
4. Round-robin (load balanced)
Cycle through agents so each gets roughly equal call volume. Requires state (which agent was last called).
Stateful pattern with external store
- request:
url: https://your.api/get-next-agent?queue=sales
method: GET
save: routing
- connect:
to: "${routing.next_agent}"
timeout: 30
- request:
url: https://your.api/release-agent
method: POST
body:
agent: "${routing.next_agent}"
call_id: "${call.id}"
Your API tracks the rotation and returns the next agent in line. On call end, mark the agent available again.
Variation: weighted round-robin
Agents with higher capacity get more calls per cycle:
agents = [
("+13105551111", 3), # gets 3 of every 6 calls
("+13105552222", 2), # gets 2 of every 6 calls
("+13105553333", 1), # gets 1 of every 6 calls
]
When to use: call centers with fungible agent skills, fairness matters. Trade-off: requires external state, slightly slower routing decision.
5. Skill-based routing
Match the caller to an agent with relevant expertise (language, certification, product knowledge). Requires caller-intent capture and agent-skill metadata.
Capture intent via IVR or AI
- prompt:
play: say:Press 1 for billing, 2 for technical support, 3 for new sales
max_digits: 1
- request:
url: https://your.api/route?skill=${prompt_value}
method: GET
save: route
- connect:
to: "${route.agent}"
timeout: 30
Capture intent via AI receptionist
- ai:
prompt:
text: |
Identify the caller's reason. Call route_to_agent with one of:
billing, technical, sales, retention.
SWAIG:
functions:
- function: route_to_agent
parameters:
type: object
properties:
skill: { type: string, enum: ["billing", "technical", "sales", "retention"] }
data_map:
webhooks:
- url: https://your.api/route
When to use: multi-product or multi-language operations, complex L1/L2/L3 support tiers. Trade-off: highest ops complexity, requires accurate skill tagging in the agent directory.
6. Geo-based routing
Route by caller's location — area code, IP/ANI lookup, or stated location.
- request:
url: https://your.api/geo-route?phone=${call.from}
method: GET
save: geo
- connect:
to: "${geo.regional_office}"
timeout: 30
The API does an area-code-to-region lookup or queries an LRN database for higher accuracy.
When to use: multi-office businesses where local presence matters, regulated industries needing in-state agents (insurance, real estate). Trade-off: area code is unreliable for mobile users; LRN lookups cost money.
7. Time-of-day routing
Different destinations during different hours. See business hours logic for the full pattern.
- cond:
when: "is_business_hours()"
then:
- connect:
to: +13105551111
else:
- connect:
to: +13105559999 # after-hours service
When to use: any operation with non-24/7 hours.
Hybrid strategies
Real production routers combine strategies. Common hybrids:
Time + skill + round-robin
inbound call
├─ if after hours → voicemail
├─ if business hours
│ └─ IVR captures skill
│ └─ round-robin among agents with that skill
│ └─ if no skill match → general queue
Geo + skill
inbound call
└─ geo-route to regional office
└─ skill-based routing within that office
└─ overflow to nearest neighboring office
Ring-all-then-hunt
inbound call
└─ ring-all 3 senior agents (15 sec timeout)
└─ if no answer, hunt through junior agents
└─ if no answer, voicemail
Comparison matrix
| Strategy | Setup complexity | Answer rate | Agent fairness | Customer experience | |---|---|---|---|---| | Simple forward | Trivial | Low | N/A | Mediocre (no redundancy) | | Ring all | Low | Highest | Poor (everyone disrupted) | Good (fastest answer) | | Hunt group | Low | Medium | Very poor | OK (with reasonable timeouts) | | Round-robin | Medium | Medium-High | Excellent | Good | | Skill-based | High | Medium-High | Configurable | Excellent | | Geo-based | Medium | Medium | Variable | Good (local touch) | | Time-of-day | Low | N/A | N/A | Critical for hours-of-operation |
Per-call decision time
Routing decision time impacts customer experience. Targets:
| Pattern | Decision overhead | |---|---| | SWML inline cond | < 50ms | | SWML + external API | 100-500ms | | SWML + AI classification | 2-5 seconds | | Multi-step IVR + API | 5-15 seconds |
Caller perception: silence under 1 second feels normal; 1-3 seconds feels like a hiccup; 3+ seconds feels broken unless filled with "one moment" TTS.
State management for stateful strategies
Round-robin and skill-based need state. Three storage options:
| Option | Latency | Durability | Complexity | |---|---|---|---| | Redis | < 5ms | Volatile | Low | | Supabase | 30-100ms | Durable | Low | | SignalWire global_data | inline | Per-call only (not persistent across calls) | Trivial | | In-memory cache | < 1ms | Lost on restart | Low |
For round-robin, a Redis counter with atomic increment is the cleanest pattern. For skill-based, durable state (Supabase) so agent skills survive restarts.
SignalWire-specific: AI agent as router
Use an AI agent as the routing layer:
- ai:
prompt:
text: |
You are a phone receptionist. Identify the caller's needs and route them.
Use route_caller to direct them based on their intent.
SWAIG:
functions:
- function: route_caller
parameters:
type: object
properties:
department: { type: string, enum: ["sales", "support", "billing"] }
urgency: { type: string, enum: ["normal", "urgent"] }
data_map:
webhooks:
- url: https://your.api/ai-route
The AI handles disambiguation, language detection, and intent capture — much better than DTMF IVR for complex routing.
Common pitfalls
- No fallback — every strategy needs a fallback (voicemail, queue, general line). When the primary fails, dead air is the worst outcome.
- Ring timeout too long — 30 seconds feels like an eternity when nothing happens. 12-18 seconds is the sweet spot.
- Round-robin without release — agents marked busy after a call but never released, queue clogs.
- Skill-based with stale skills — agent skills change, the routing table doesn't. Add an admin UI for self-service skill updates.
- Geo-based on mobile-only callers — area code accuracy is poor for cell users. Ask the caller for location during the IVR.
Related patterns
- Business hours logic — time-of-day routing
- Warm transfer and cold transfer
- Callback scheduling
- SWML connect verb
- SignalWire call flow builder
References
- SignalWire SWML
connectverb documentation - ACD (Automatic Call Distributor) literature — standard call-center routing models