Merging or correlating multiple call legs
This guide explains how VoIPmonitor identifies and links multiple call legs belonging to the same end-to-end call.
ℹ️ Note: Header-based correlation is disabled by default. callidmerge_header, matchheader and call_id_alternative are all empty unless you explicitly set them in voipmonitor.conf. VoIPmonitor recognizes no SIP header for merging or correlation by default — a custom header is used only if its exact name is configured in one of these options. (Legs that share the same Call-ID are merged automatically; everything else requires configuration.)
Overview
| Scenario | Description | VoIPmonitor Behavior |
|---|---|---|
| Same Call-ID | Multiple legs share identical SIP Call-ID | Automatically treated as one CDR (no configuration needed) |
| Different Call-IDs | SBC/B2BUA generates new Call-ID for each leg | Creates separate CDRs (requires correlation configuration) |
Same Call-ID Behavior
When multiple legs share the same SIP Call-ID, VoIPmonitor automatically treats them as a single call:
- Result: One CDR in the GUI, all packets in one PCAP file
- BYE handling: When a BYE is received, VoIPmonitor finalizes the CDR for that Call-ID
- No configuration needed: This is automatic and cannot be disabled
ℹ️ Note: If you want separate CDRs per leg, configure your PBX/SBC to generate unique Call-IDs, then use the correlation methods below.
Correlation Methods for Different Call-IDs
When calls pass through an SBC/B2BUA that generates new Call-IDs, choose one of these methods:
| Method | Result | Use Case |
|---|---|---|
| Linking | Separate CDRs, visually linked in GUI | View each leg independently with navigation |
| Merging | Single CDR and PCAP file | Treat entire call as one record |
Method 1: Linking Separate CDRs
Keeps legs as distinct database entries but associates them visually.
Automatic Linking (Legs by CID)
Built-in, no configuration required:
- Matches CDRs where last 6 digits of caller/called numbers match within ±10 seconds
- Found in CDR detail view → Legs by CID tab
- Use Merge button to combine linked legs into unified SIP history view
Custom Header Linking
Link legs using a shared SIP header. Results appear in Legs by header tab.
GUI Configuration (no restart required):
- Navigate to Settings → Custom Headers → CDR
- Add the header name (e.g.,
X-Correlation-ID) - Enable "match in SIP by header" checkbox
voipmonitor.conf (requires restart):
# Store header in cdr_next.match_header for leg matching
matchheader = X-Correlation-ID
# Alternative Call-ID for correlation when standard Call-ID changes
call_id_alternative = X-Correlation-ID
Method 2: Merging into Single CDR
Combines multiple legs into one CDR and one PCAP file. Requires the second leg's INVITE to contain a header with the first leg's Call-ID.
Configuration (voipmonitor.conf):
# Header containing parent Call-ID in outbound INVITEs
callidmerge_header = X-PARENT-CALLID
# Optional: Encrypt header value
#callidmerge_secret = yourSecretString
PBX Configuration Example (Asterisk):
[macro-add-parent-callid]
exten => s,1,NoOp(Adding Parent-Call-ID header)
exten => s,n,Set(ORIG_CALLID=${SIPHEADER(Call-ID)})
exten => s,n,SipAddHeader(X-PARENT-CALLID: ${ORIG_CALLID})
exten => s,n,MacroExit()Comparison: Linking vs Merging
| Feature | Linking (matchheader) | Merging (callidmerge_header) |
|---|---|---|
| Database entries | Separate CDRs | Single CDR |
| PCAP files | Separate files | Single merged file |
| GUI navigation | "Legs by header" tab | Single call view |
| PBX requirement | Add correlation header | Add header with parent Call-ID |
| Restart required | GUI: No / Config: Yes | Yes |
MOS Analysis in Multi-Leg Calls
Default Behavior
When several legs end up in one CDR (same Call-ID, or merged with callidmerge_header), the CDR quality columns (MOS, loss, jitter) describe only one selected stream per direction – stream A (caller → called) and stream B (called → caller). Which streams are chosen is described in How the caller (A) and called (B) streams are selected. Quality issues on the other legs are therefore not visible in the CDR columns; the statistics of every stream remain in the cdr_rtp table and in the RTP legs tab of the CDR detail.
Solution 1: call_branches (Experimental)
Evaluate each leg separately:
call_branches = yes
⚠️ Warning: The call_branches option is experimental. Test before deploying to production.
Solution 2: Separate CDRs per Leg
Generate unique Call-IDs for each leg to create separate CDRs:
- Each CDR shows exact packet loss/MOS for that specific leg
- Link related CDRs using
matchheaderfor navigation - Best for detailed per-stream quality analysis
| Approach | MOS Visibility | Complexity |
|---|---|---|
| Default merged CDR | One selected stream per direction (A/B) | Low |
| call_branches = yes | Per-branch evaluation | Low (experimental) |
| Separate CDRs + linking | Individual MOS per leg | High (requires PBX changes) |
Transferred Calls: Preserving Audio
When calls are transferred (REFER/re-INVITE), BYE may terminate the original leg while transfer continues. To preserve audio:
# Continue recording RTP after BYE
ignore_rtp_after_bye_confirmed = no
# Continue duration counting after BYE
ignore_duration_after_bye_confirmed = no
💡 Tip: If audio for transferred portion is missing after configuring correlation, add these options and restart the sniffer.
IP-Based Access Control
When using filter_ip user restrictions, unique Call-IDs per leg are required for proper security.
- Problem
- If legs share the same Call-ID, users can download PCAPs containing traffic from IPs outside their permission (all leg traffic is combined).
- Solution
- Configure PBX/SBC to generate unique Call-IDs per network leg segment. Each CDR then contains only traffic from specific IPs.
⚠️ Warning: Do NOT use callidmerge_header when IP-based access control is required. Merging defeats the security purpose.
See User_Management for configuring IP-based restrictions.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| One CDR instead of two | Legs share same Call-ID | Expected behavior; configure unique Call-IDs if separate CDRs needed |
| Audio missing after transfer | BYE terminates recording | Set ignore_rtp_after_bye_confirmed = no
|
| Correlation not working | Header mismatch or not present | Verify header in PCAP; check exact spelling; restart sniffer for config changes |
| Capture stops on BYE | Same Call-ID = same session | For independent tracking, use unique Call-IDs per leg |
See Also
AI Summary for RAG
Summary: VoIPmonitor call correlation handles multi-leg calls in two scenarios: (1) Same Call-ID legs are automatically treated as one CDR; (2) Different Call-IDs (from SBC/B2BUA) require configuration. Two methods exist: Linking (matchheader) keeps separate CDRs visually associated via "Legs by header" tab; Merging (callidmerge_header) combines legs into single CDR where second INVITE contains parent Call-ID header. For MOS visibility in merged calls, default shows only longest stream; use call_branches=yes (experimental) or separate CDRs for per-leg analysis. Transferred calls may lose audio if BYE terminates recording; fix with ignore_rtp_after_bye_confirmed=no. For IP-based access control (filter_ip), unique Call-IDs per leg are required to prevent unauthorized PCAP access.
Keywords: call correlation, leg matching, Call-ID, matchheader, call_id_alternative, callidmerge_header, ignore_rtp_after_bye_confirmed, call_branches, MOS, multi-leg call, Legs by CID, Legs by header, B2BUA, SBC, CDR merging, BYE termination, call transfer, filter_ip, IP restrictions, packet loss per stream
Key Questions:
- Why do I see only one CDR when my call has multiple legs?
- How to link call legs with different Call-IDs?
- What is the difference between matchheader and callidmerge_header?
- How to merge multiple call legs into a single CDR?
- Why is MOS only showing for the longest RTP stream?
- How to see MOS for individual legs in a merged CDR?
- Why is audio missing for transferred calls?
- How to preserve audio after BYE during call transfer?
- Why can users with IP restrictions download PCAPs with unpermitted traffic?
- How to view exact packet loss per RTP stream?