SIP Telephony — Zero to Hero v1 · 2026-04-25
PART 09 · STEPS 96–100

Customer Deep Dives

Four real Freya deployments, one per trunk topology you will meet in production: a public-internet trunk (Allianz), a stateless SIP proxy in front of a Cisco call center (Garanti), an MPLS-fed Genesys SBC (Anadolu Sigorta), and a single-host on-prem box on a wholesale TR carrier (KKB). Then the capstone: trace one outbound call through every layer at once.

5 steps 3 demos ~20 minutes
96customer comparison matrix

Four customers, four trunk topologies

Every customer SIP integration is a variation on the same three knobs: where the carrier sits (public internet, on-net via firewall, MPLS, or single-host public IP), how authentication works (IP allowlist or registered credentials), and which transport actually carries the signaling (UDP vs TCP — the most common silent failure). The table below is the cheat sheet. Click any row to jump to that customer's deep dive.

4
customer trunks
3
network paths (internet · MPLS · firewall)
2
POCs done · 1 running · 1 pre-contract
10000–10499
RTP range (consistent across all 4)
Demo 1 · Customer comparison matrix
click row to jump
Customer Trunk Provider Provider IP(s) Network Path Transport RTP Codec Status
Allianz Connecta 31.155.2.249 Public internet UDP/TCP (TBD) 10000–10499 ulaw / alaw Pre-contract
Garanti BBVA KASP + Cisco VVB 10.231.16.153 · 10.231.16.148 IP-routed (firewall) UDP + TCP 10000–10499 ulaw / alaw POC done
Anadolu Sigorta Genesys SBC 10.1.137.60/61 + 4× /32 MPLS circuit TCP (silent) 10000–10499 ulaw / alaw POC running
KKB / Kloudeks Verimor 194.49.126.26 Public internet UDP/TCP 10000–10499 ulaw / alaw POC running
Pattern: the customer side changes (public PSTN trunk vs enterprise call-center stack), the Asterisk side stays almost identical. Status pills: green = POC complete, amber = active POC, blue = pre-contract.
96Allianz · Connecta

Allianz — Connecta SIP Trunk (cloud-hosted)

The simplest topology in the lineup. Allianz's CRM owns campaign creation; Freya is the voice runtime; Connecta is the Turkish telecom partner that hands us a SIP trunk over the public internet. There is no VPN, no SBC, no MPLS — just an IP-allowlisted trunk to a Connecta-side gateway at 31.155.2.249.

Trunk providerConnecta (TR telecom partner)
Provider IP31.155.2.249 (public internet)
AuthIP-based, no credentials
TransportUDP/TCP — confirmed in onboarding mail thread
Inbound DID0216 / 0850-399-99-99 (444) / contact-center numbers — pending Allianz selection
Outbound prefixConnecta-issued routing prefix (e.g. 9015)
RoutingAllianz CRM → Connecta → public internet → Freya Asterisk → voice-agent
Encryptionnone currently
StatusPre-contract · 2026-04-24

Topology

Trunk topology · Allianz / Connecta
hover any node
Freya Asterisk port 5060 RTP 10000–10499 public internet 5060/UDP Connecta 31.155.2.249 prefix 9015 Allianz CRM campaigns + PSTN numbers SIP/RTP PSTN CRM API
Public-internet trunk: the only thing standing between the carrier and Freya is Asterisk's identify ACL. Lock the firewall down to Connecta's /32 and nothing else.

Things you must remember

  • Connecta is a trunk, not a PBX. Allianz's CRM still owns campaign creation, destination lists, and outcome write-back. Freya is just the voice runtime sitting between the trunk and the AI agent.
  • Public-internet trunk = strict ACL. Only Connecta's 31.155.2.249 may reach our 5060. Lock the firewall by source IP and use the matching identify block in pjsip.conf so unsolicited INVITEs are dropped.
  • The 9015 prefix. Outbound dialed numbers go out as e.g. 9015-0532-1234567. The dialplan needs a rewrite rule to insert/strip the prefix or Connecta will route the call to the wrong destination.
  • DID choice has product implications. If Allianz picks the 444 number, callbacks land in their existing call center. If they pick a fresh 0216 number, callbacks come back to Freya. This is a business decision being made on their side.
Try this on KKB
$ # The KKB pjsip.conf has the same shape Allianz will use. Inspect:
$ ssh freya@192.168.35.197 "docker exec freya-asterisk asterisk -rx 'pjsip show endpoints'"
$ # Find the [providers] endpoint - that's the trunk. Note the identify match line.
$ # When Allianz signs, we'll add a near-identical block pinned to 31.155.2.249.

Reference: notes/customers/allianz/2026-03-31-1017-allianz-connecta-sip-trunk-notes.md

97Garanti · KASP + VVB

Garanti BBVA — Cisco UCCE + KASP Light Proxy

Garanti is the most enterprise-flavoured topology in the set. The customer telephony stack is Cisco UCCE with a VVB (Virtual Voice Browser) sitting between the call center and the SIP world, and a KASP (Kamailio-based) SIP proxy in light/stateless mode in front of it. Freya's Asterisk pod is on the Garanti OpenShift cluster, on the same routed network. Splunk gives the customer full packet visibility.

Customer telephony stackCisco UCCE call center + VVB voice browser
SIP proxyKASP at 10.231.16.153/32light / stateless mode
Voice browserCisco VVB at 10.231.16.148/32
Asterisk node IP10.231.x.120 (node-label pinned for stable IP across restarts)
Network pathIP-routed via Garanti firewall · full packet visibility through Splunk
TransportSIP 5060 UDP and TCP — both opened
RTP rangeAsterisk 10000–10499 · Cisco VVB 13000–13499
Codeculaw / alaw
DeploymentOpenShift 4.20, namespace freya-poc
StatusPOC complete · 2026-04-17

Topology — and the M08 lesson visualized

Trunk topology · Garanti BBVA (M08 lesson)
hover any node
Freya Asterisk 10.231.x.120 force_rport=no KASP (light) 10.231.16.153 no Record-Route Cisco VVB 10.231.16.148 RTP 13000–13499 Cisco UCCE call-center IVR 2→7→9 INVITE (proxy path) ACK / BYE bypass (M08)
The dashed red lines are the M08 trap: KASP in light mode does not insert Record-Route, so once the dialog is established, ACK and BYE bypass the proxy and go UA-to-UA between Asterisk and VVB. The customer firewall opened 5060 only on the proxy path; the direct UA path was blocked. ACK never arrived. rtptimeout=60 closed the call at exactly 60 s. Fix: open UDP 5060 directly between VVB and Asterisk.

The Garanti M08 story (read it once, remember it forever)

  • Symptom. Every call dropped at exactly 60 seconds.
  • Six hypotheses tested. Firewall ACL gap, NAT/address rewrite missing, stateless proxy bypassing ACK, codec/transport mismatch, identify match miss, header parse failure.
  • Root cause. KASP light mode does not insert Record-Route. ACK and BYE bypass the proxy and go direct VVB↔Asterisk. Customer firewall opened 5060 only on the proxy path. ACK never arrived. rtptimeout=60 did its job.
  • Fix. Open UDP 5060 directly between VVB and Asterisk in addition to the proxy path.

Garanti-tuned PJSIP knobs

KnobDefaultGarantiWhy
force_rportyesnoSIP proxy is in the middle. Don't let Asterisk override the source-port hint from the proxy.
rewrite_contactyesnoIf we rewrite Contact to the packet source, downstream hops get confused. Trust what the proxy puts in headers.
Try this on KKB
$ # See the same knobs on KKB endpoints (default values, since KKB has no proxy):
$ ssh freya@192.168.35.197 "docker exec freya-asterisk asterisk -rx 'pjsip show endpoint providers'" \
    | grep -E 'force_rport|rewrite_contact|rtp_timeout'

Reference: freya-onprem/openshift/garanti/, freya-onprem/playbooks/sip-asterisk-debug.md

98Anadolu · Genesys + MPLS

Anadolu Sigorta — Genesys SBC + MPLS

The customer telephony stack here is Genesys (call center + SBC, on Cisco UCCE Appliance OS variant). Freya's Asterisk sits at 10.31.39.14; the Genesys SBC is at 10.1.137.60/61 (two interfaces on one device) plus four Genesys generic /32s. The path between them is an MPLS circuit, which means firewall-transparent at the port level — the customer firewall does not enforce TCP/UDP rules on this path. ACLs only live on the network gear.

Customer telephony stackGenesys call center + Genesys SBC (Cisco UCCE Appliance variant)
SBC IPs10.1.137.60/61 + 4× Genesys generic /32s
Asterisk node IP10.31.39.14 (node-label pinned)
Network pathMPLS circuit · firewall-transparent · ACLs only on network gear
TransportSIP 5060 TCP (initially labeled UDP, silently enforced as TCP — see A20)
RTP rangeAsterisk 10000–10499 · SBC 16384–48000
Inbound DID0850-724-0850 (test only, reverted) — never attach the live customer-service DID
Trunk codes5001 outbound · 5002 inbound · 5003 outbound-transfer · 5004 inbound-transfer
Direction headerX-Freya-Direction (whitelisted; per-trunk policy)
Codeculaw / alaw
DeploymentOpenShift 4.18+, namespace freya-poc
StatusPOC running · concurrent-call testing

Topology

Trunk topology · Anadolu Sigorta (MPLS · TCP trap)
hover any node
Freya Asterisk 10.31.39.14 5001/5002 MPLS circuit İşnet A20: TCP enforced, label said UDP Genesys SBC 10.1.137.60/61 + 4 generics Genesys call center A4: header chars trap SIP 5060 TCP SIP 5060 TCP
MPLS makes the network look transparent at port level, but the upstream İşnet ACL silently enforces TCP — even when the firewall ticket says UDP. Always confirm transport with tcpdump on the wire before debugging anywhere else.

Two traps you must remember

A20 — UDP/TCP confusion

Customer ticket said "open 5060 UDP". İşnet (outsourced firewall) actually enforced TCP. Symptom: silent failure, no SIP arrives, no error. Burcu (İşnet) fixed it live during the 2026-04-03 debug call. Lesson: always confirm transport actively with tcpdump before anything else.

A4 — non-ASCII header names

Genesys's X-Genesis-… headers contained Turkish characters in field names (not values — names). PJSIP silently dropped the entire INVITE. Fix: ASCII-only header names. Lesson: when an INVITE silently disappears, capture it on the wire and look at the bytes — your dialplan never saw it.

Try this on KKB
$ # The "always confirm transport" check:
$ ssh freya@192.168.35.197 "sudo tcpdump -i any -nn -v 'tcp port 5060 or udp port 5060'" 2>&1 | head -30

Reference: notes/customers/anadolu-sigorta/on-prem-blueprint.md, 2026-04-03-1404-anadolu-sigorta-sip-debug-notes.md

99KKB · Verimor

KKB / Kloudeks — Verimor Trunk

This is the box you have an SSH key to: kkbfcfreyasrv01 (192.168.35.197 internal, 185.199.89.19 public). Single-host Docker Compose deployment, 4× H100 NVL, Gemma LLM. The trunk is Verimor, a Turkish wholesale PSTN carrier, reached over the public internet at 194.49.126.26. Outbound caller-ID is 908502427127.

Trunk providerVerimor (TR PSTN)
Provider IP194.49.126.26/32
Caller-ID908502427127
Asterisk public IP (egress)185.199.89.19
TransportSIP 5060 UDP/TCP · RTP 10000–10499 UDP
Codeculaw / alaw
DeploymentSingle-host Docker Compose at kkbfcfreyasrv01
Status (2026-04-24)POC running · concurrency tests show carrier capping

Topology

Trunk topology · KKB / Verimor
hover any node
Freya Asterisk 185.199.89.19 RTP 10000–10499 public internet 5060/UDP Verimor 194.49.126.26 CID 908502427127 PSTN subscribers in TR
Same shape as Allianz: single trunk, no SBC. The wrinkle here is the per-caller-ID concurrency cap and the persistent block on five destinations — both visible in the 2026-04-24 outbound replay below.

The 2026-04-24 outbound concurrency tests

Three burst campaigns at 16:31, 18:26, and 20:38 UTC. The Freya stack itself was healthy throughout — voice agents, dashboard, campaign worker, Asterisk all green. The failures originate at the carrier (Verimor), plus a number-format bug that broke the first burst entirely.

Demo 3 · KKB campaign replay (2026-04-24)
16:31 UTC
2464e8d2
0% 0 of 9
18:26 UTC
d8cb2cf4
44% 4 of 9
20:38 UTC
879c7bb0
60% 6 of 10
200 OK · connected 603 declined · cause 41 404 not found · cause 3 408 timeout · cause 18
data from kkb-outbound-concurrency-2026-04-24 report · the 16:31 burst is the 94350 prefix bug visualized: every dialed string was malformed before it reached the trunk, so the 4 unique numbers got a 404 (cause 3 — unallocated) and the 5 always-blocked numbers still got a 603 because the tail digits matched.

What 2026-04-24 told us

  • Not a Freya-side network problem. Voice agents (30 containers), dashboard, campaign worker, Asterisk — all healthy. Pre-dial path completed successfully on every attempt, including the failed ones.
  • Same five destinations rejected across all three campaigns. +905374705251, +905510154221, +905532890209, +905053864612, +905535918355 all returned SIP 603 / Q.850 cause 41 in every test. Stable carrier-side block, not transient congestion.
  • Number-format bug in the 16:31 campaign. Every destination dialed with a stray 94350 prefix instead of +90. Result: zero connections.
  • One transient 408 on +905537721287 at 20:38. Same number connected fine at 18:26, so it was a handset/PSTN issue, not a trunk issue.

Open work items at KKB

  1. Open a Verimor ticket with the five blocked numbers and ask about per-caller-ID concurrency caps.
  2. Find and fix the source of the 94350 prefix (campaign upload path or dialplan rewrite).
  3. Throttle outbound dialing on the campaign worker (e.g. 2 calls/sec/caller-ID).
  4. Surface SIP response codes + Q.850 cause in the dashboard so future triage doesn't need shell access.

Full report: notes/customers/kloudeks-kkb/reports/kkb-outbound-concurrency-2026-04-24/

100capstone · trace one call

Final — trace a live call through every layer

The capstone exercise. Place one outbound test call on KKB and account for every hop. Four SSH terminals, one dashboard tab, one synchronized timeline. If you can read all four streams in parallel and tell the story of the call, you are a SIP debugger.

Setup — four parallel SSH sessions

Open four terminals on your laptop. Each one tails a different layer of the stack on kkbfcfreyasrv01:

T1 · SIP messages
$ ssh freya@192.168.35.197 \
  "docker exec freya-asterisk \
   asterisk -rx 'pjsip set logger on'"
$ ssh freya@192.168.35.197 \
  "docker logs -f freya-asterisk" \
  | grep --line-buffered \
    -E 'INVITE|ACK|BYE|200 OK|180|183|603|404|408|486'

→ INVITE sip:+905...@194.49.126.26
← SIP/2.0 100 Trying
← SIP/2.0 183 Session Progress
← SIP/2.0 200 OK
→ ACK sip:+905...@...
... 32s of media ...
→ BYE sip:+905...@...
← SIP/2.0 200 OK
T2 · pcap
$ ssh freya@192.168.35.197 \
  "sudo tcpdump -i any -w /tmp/capstone.pcap \
   udp portrange 10000-10499 or port 5060"

tcpdump: listening on any, link-type
LINUX_SLL2 (Linux cooked v2)
0  packets dropped by kernel
... capturing ...
^C
3214 packets captured
3221 packets received by filter
0 packets dropped by kernel
T3 · campaign worker
$ ssh freya@192.168.35.197 \
  "docker logs -f freya-campaign-worker"

[INFO] Processing initiate_call job
       7e2f4a8c-...  outbound dial=+9053...
[INFO] initiate_call: in_progress
[INFO] Stored call_state in Valkey
[INFO] Awaiting ARI StasisStart for
       PJSIP/providers-00000017
[INFO] Bridged call to voice-agent
       kkb-freya-voice-agent-3
[INFO] initiate_call: connected
... 32s ...
[INFO] initiate_call: completed
       duration=32.4s code=200
T4 · voice-agent
$ ssh freya@192.168.35.197 \
  "docker logs -f kkb-freya-voice-agent-1"

[boot] resolve_config starting
[boot] register_call ok
[boot] generate_first_message ok
[boot] Stored boot manifest in Valkey
[pipe] WebSocket /ai_media accepted
[pipe] STT engaged · partial = 'merhaba'
[pipe] LLM step 1 · 482ms
[pipe] TTS engaged · sent 18 frames
[pipe] STT engaged · partial = 'evet'
[pipe] LLM step 2 · 419ms
... 12 turns ...
[hangup] BYE received from peer
[hangup] MixMonitor stopped

What you should see, in order

  1. Campaign worker (T3) · Processing initiate_call job <uuid>initiate_call: in_progress. The dashboard's "Place call" button has reached the worker.
  2. Voice-agent (T4) · Boot step 'resolve_config' startingregister_callgenerate_first_messageStored boot manifest in Valkey. Per-call agent process is ready before the SIP leg even starts ringing.
  3. Asterisk SIP log (T1) · outbound INVITE to 194.49.126.26 (Verimor) → 100 Trying183 Session Progress200 OKACK. The full happy-path SDP exchange.
  4. Asterisk channel log · Channel PJSIP/providers-… joined 'simple_bridge'Channel WebSocket/ai_media-… joined 'simple_bridge'. The trunk leg and the agent leg are now in the same bridge.
  5. Voice-agent (T4) · STT + LLM + TTS activity logs. The pipeline turns are now real-time. Each STT partial / LLM call / TTS frame should land within hundreds of milliseconds.
  6. pcap (T2) · UDP 10000–10499 packets in both directions, ~50/sec each way. RTP is flowing. If this layer is silent while T1 says "200 OK", you have a media-path problem (firewall, NAT, codec).
  7. Hangup · SIP BYE200 OKEnd MixMonitor Recording. Clean teardown. If T1 shows the BYE but T3/T4 never see "completed", you have a state-machine problem in the campaign worker or the agent.

If any of those is missing, you have a specific layer to investigate. The whole thing is a state machine, and now you can read every transition.

What you do after the call
$ ssh freya@192.168.35.197 "docker exec freya-asterisk asterisk -rx 'pjsip set logger off'"
$ scp freya@192.168.35.197:/tmp/capstone.pcap .
$ open capstone.pcap          # opens in Wireshark on macOS

In Wireshark: Telephony → VoIP Calls → your call → Flow Sequence. Compare what you see there to the order above. They should agree exactly. The pcap is the ground truth — your terminal logs are interpretations of the same wire data, and Wireshark is the unfiltered version.

Capstone checkpoint

You place an outbound test call. T1 shows INVITE100 Trying200 OKACK. T2 shows packets only on port 5060, none in the 10000–10499 range. T3 says "in_progress" but never "connected". T4 shows the voice-agent booting but never engaging STT. Where is the bug?

Show answer

Media-path failure. Signaling is healthy (T1 is clean), but RTP isn't flowing (T2 has no port 10000–10499 packets), so the bridge never carries audio and the agent never hears the user. Check: (1) firewall ACL covers UDP 10000–10499 in both directions, (2) the SDP offer/answer in the INVITE/200 OK negotiated a codec both sides accept (ulaw/alaw), (3) Asterisk's rtp.conf port range matches the firewall rule. The campaign worker reports "in_progress" because the SIP leg is technically up; it never moves to "connected" because no media bridge events arrive.

You are now a SIP debugger.