SIP VAULT CDR Integration Guide
This guide covers integrating SIP VAULT with CDR Viewer applications so that operators can open call troubleshooting data directly from CDR records with a single click.
Table of Contents
- Overview
- How It Works
- PHP Integration (OpenSIPS Control Panel)
- Python Integration
- Node.js Integration
- OpenSIPS CP CDR Viewer Template Modification
- Custom CDR Viewer Integration
- URL Parameters Explained
- Testing the Integration
- Troubleshooting
Overview
SIP VAULT provides a "deep link" mechanism that allows CDR Viewer applications to generate authenticated URLs pointing directly to a specific call's troubleshooting data. When an operator clicks the link, the SIP VAULT dashboard opens with the full SIP capture, RTCP quality analysis, and OpenSIPS logs for that call.
The integration is lightweight: a single function that takes a Call-ID, computes an HMAC-SHA256 signature with a timestamp, and returns a URL. No API calls, no callbacks, no session state. The call date does not need to be passed — the API resolves it from S3 automatically.
How It Works
1. Operator views CDR list in CDR Viewer (e.g., OpenSIPS Control Panel)
2. Each CDR row has a "SIP VAULT" button
3. On click, the CDR Viewer server generates an HMAC-signed URL:
a. Hash the Call-ID: call_hash = SHA-256(call_id)[:16]
b. Get current hour: ts_hour = unix_timestamp / 3600 (integer division)
c. Build payload: "{call_id}:{ts_hour}"
d. Sign the payload: sig = HMAC-SHA256(secret, payload).hex()[:32]
e. Build the URL: https://sipvault.example.com/call/{call_hash}
?cid={customer_id}&callid={call_id}&ts={ts_hour}&sig={sig}
4. User's browser opens the URL
5. SIP VAULT dashboard validates the signature and loads the call data
The HMAC secret used for signing must match the SIPVAULT_HMAC_SECRET configured in /etc/sipvault/api.env on the SIP VAULT server. The secret never leaves the server side.
Tokens are valid for ±1 hour from the hour in which they were generated.
PHP Integration (OpenSIPS Control Panel)
This is the primary integration for OpenSIPS Control Panel (OpenSIPS CP).
Installation
Copy the file deploy/cdr-integration/generate_link.php to your OpenSIPS CP installation.
Configuration
Edit the constants at the top of the file to match your deployment:
define('SIPVAULT_BASE_URL', 'https://sipvault.sippulse.com.br');
define('SIPVAULT_HMAC_SECRET', 'your_hmac_secret_here'); // Must match api.env
define('SIPVAULT_CUSTOMER_ID', 'sippulse');
Full Source Code
<?php
/**
* SIP VAULT link generator for OpenSIPS CP / CDR Viewer integration.
*
* Usage in CDR list template:
* <?php echo sipvault_link($row['call_id']); ?>
*
* Or as a button:
* <a href="<?php echo sipvault_link($cdr['callid']); ?>"
* target="_blank" class="btn btn-sm btn-info">
* SIP VAULT
* </a>
*/
// Configuration — must match deploy/api.env SIPVAULT_HMAC_SECRET
define('SIPVAULT_BASE_URL', 'https://sipvault.sippulse.com.br');
define('SIPVAULT_HMAC_SECRET', 'YOUR_HMAC_SECRET_HERE');
define('SIPVAULT_CUSTOMER_ID', 'sippulse');
/**
* Generate an HMAC-signed SIP VAULT dashboard URL for a call.
*
* @param string $call_id The SIP Call-ID header value
* @param string $call_time Unused — kept for API compat. The API resolves the date from S3.
* @param string $customer Customer identifier (default: SIPVAULT_CUSTOMER_ID)
* @return string Full URL with authentication token (valid ~1 hour)
*/
function sipvault_link($call_id, $call_time = null, $customer = SIPVAULT_CUSTOMER_ID) {
$call_hash = substr(hash('sha256', $call_id), 0, 16);
$ts_hour = (int)(time() / 3600);
$payload = "{$call_id}:{$ts_hour}";
$sig = substr(hash_hmac('sha256', $payload, SIPVAULT_HMAC_SECRET), 0, 32);
return SIPVAULT_BASE_URL . '/call/' . $call_hash
. '?cid=' . urlencode($customer)
. '&callid=' . urlencode($call_id)
. '&ts=' . $ts_hour
. '&sig=' . $sig;
}
/**
* Generate an HTML button/link for the CDR table.
*/
function sipvault_button($call_id, $call_time = null, $customer = SIPVAULT_CUSTOMER_ID) {
$url = sipvault_link($call_id, $call_time, $customer);
return '<a href="' . htmlspecialchars($url) . '" target="_blank" '
. 'class="btn btn-sm btn-info" title="Open in SIP VAULT">'
. '<i class="fa fa-search"></i> SIP VAULT</a>';
}
// CLI usage
if (php_sapi_name() === 'cli' && isset($argv[1])) {
$customer = isset($argv[2]) ? $argv[2] : SIPVAULT_CUSTOMER_ID;
echo sipvault_link($argv[1], null, $customer) . "\n";
}
Usage in Templates
Simple link:
Button with icon:
Inline HTML:
<a href="<?php echo sipvault_link($cdr['callid']); ?>"
target="_blank" class="btn btn-sm btn-info">
<i class="fa fa-search"></i> SIP VAULT
</a>
CLI Testing
Python Integration
For CDR Viewer applications built with Python (Django, Flask, FastAPI, etc.).
Installation
Copy deploy/cdr-integration/generate_link.py to your project.
Configuration
Edit the constants at the top of the file:
SIPVAULT_BASE_URL = "https://sipvault.sippulse.com.br"
SIPVAULT_HMAC_SECRET = "your_hmac_secret_here" # Must match api.env
Full Source Code
"""
SIP VAULT link generator for CDR Viewer integration.
Usage:
from generate_link import sipvault_link
url = sipvault_link(
call_id="a7a186b1ff8c4cfeaa90a9144ba9cb24",
customer_id="sippulse",
)
"""
import hashlib
import hmac
import time
from urllib.parse import quote
# Configuration — match deploy/api.env SIPVAULT_HMAC_SECRET
SIPVAULT_BASE_URL = "https://sipvault.sippulse.com.br"
SIPVAULT_HMAC_SECRET = "YOUR_HMAC_SECRET_HERE"
def sipvault_link(
call_id: str,
call_date: str | None = None, # unused, kept for API compat
customer_id: str = "sippulse",
secret: str = SIPVAULT_HMAC_SECRET,
base_url: str = SIPVAULT_BASE_URL,
) -> str:
"""Generate an HMAC-signed SIP VAULT dashboard URL for a call.
Args:
call_id: The SIP Call-ID header value
call_date: Ignored (the API resolves the date from S3). Kept for compat.
customer_id: Customer identifier
secret: HMAC secret (must match API config)
base_url: SIP VAULT dashboard base URL
Returns:
Full URL with authentication token (valid ~1 hour)
"""
call_hash = hashlib.sha256(call_id.encode()).hexdigest()[:16]
ts_hour = int(time.time()) // 3600
payload = f"{call_id}:{ts_hour}".encode()
sig = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()[:32]
return (
f"{base_url}/call/{call_hash}"
f"?cid={quote(customer_id)}&callid={quote(call_id)}&ts={ts_hour}&sig={sig}"
)
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python generate_link.py <call_id> [customer_id]")
sys.exit(1)
cid = sys.argv[2] if len(sys.argv) > 2 else "sippulse"
print(sipvault_link(call_id=sys.argv[1], customer_id=cid))
Usage in Django
from generate_link import sipvault_link
def cdr_detail(request, cdr_id):
cdr = CDR.objects.get(id=cdr_id)
vault_url = sipvault_link(
call_id=cdr.call_id,
customer_id="acme",
)
return render(request, "cdr_detail.html", {"cdr": cdr, "vault_url": vault_url})
Usage in Flask
from generate_link import sipvault_link
@app.route("/cdr/<int:cdr_id>")
def cdr_detail(cdr_id):
cdr = get_cdr(cdr_id)
vault_url = sipvault_link(call_id=cdr["call_id"])
return render_template("cdr_detail.html", cdr=cdr, vault_url=vault_url)
CLI Testing
Node.js Integration
For CDR Viewer applications built with Node.js (Express, Next.js, etc.).
Installation
Copy deploy/cdr-integration/generate_link.js to your project.
Configuration
Set environment variables or edit the defaults in the file:
export SIPVAULT_BASE_URL="https://sipvault.sippulse.com.br"
export SIPVAULT_HMAC_SECRET="your_hmac_secret_here"
export SIPVAULT_CUSTOMER_ID="sippulse"
Full Source Code
/**
* SIP VAULT link generator for Node.js / Next.js CDR integration.
*
* Usage:
* import { sipvaultLink } from './generate_link'
* const url = sipvaultLink('call-id-here')
*/
const crypto = require('crypto')
// Configuration — must match deploy/api.env SIPVAULT_HMAC_SECRET
const SIPVAULT_BASE_URL = process.env.SIPVAULT_BASE_URL || 'https://sipvault.sippulse.com.br'
const SIPVAULT_HMAC_SECRET = process.env.SIPVAULT_HMAC_SECRET || 'YOUR_HMAC_SECRET_HERE'
const SIPVAULT_CUSTOMER_ID = process.env.SIPVAULT_CUSTOMER_ID || 'sippulse'
/**
* Generate an HMAC-signed SIP VAULT dashboard URL for a call.
*
* @param {string} callId - The SIP Call-ID header value
* @param {string} [_callDate] - Unused. Kept for API compat. The API resolves date from S3.
* @param {string} [customerId] - Customer identifier
* @returns {string} Full URL with authentication token (valid ~1 hour)
*/
function sipvaultLink(callId, _callDate, customerId = SIPVAULT_CUSTOMER_ID) {
const callHash = crypto.createHash('sha256').update(callId).digest('hex').substring(0, 16)
const tsHour = Math.floor(Date.now() / 1000 / 3600)
const payload = `${callId}:${tsHour}`
const sig = crypto.createHmac('sha256', SIPVAULT_HMAC_SECRET).update(payload).digest('hex').substring(0, 32)
return (
`${SIPVAULT_BASE_URL}/call/${callHash}`
+ `?cid=${encodeURIComponent(customerId)}`
+ `&callid=${encodeURIComponent(callId)}`
+ `&ts=${tsHour}`
+ `&sig=${sig}`
)
}
module.exports = { sipvaultLink }
// CLI usage
if (require.main === module) {
const callId = process.argv[2]
if (!callId) {
console.error('Usage: node generate_link.js <call_id> [customer_id]')
process.exit(1)
}
const customerId = process.argv[3] || SIPVAULT_CUSTOMER_ID
console.log(sipvaultLink(callId, null, customerId))
}
Usage in Express
const { sipvaultLink } = require('./generate_link')
app.get('/cdr/:id', (req, res) => {
const cdr = getCDR(req.params.id)
const vaultUrl = sipvaultLink(cdr.callId)
res.render('cdr_detail', { cdr, vaultUrl })
})
Usage in Next.js API Route
import { sipvaultLink } from '../../lib/generate_link'
export default function handler(req, res) {
const { callId } = req.query
const url = sipvaultLink(callId)
res.json({ url })
}
CLI Testing
OpenSIPS CP CDR Viewer Template Modification
To add the SIP VAULT button to the OpenSIPS Control Panel CDR Viewer, modify the CDR list template.
Step 1: Copy the Link Generator
Copy deploy/cdr-integration/generate_link.php to your OpenSIPS CP installation directory, typically:
Step 2: Edit the CDR Viewer Template
Edit the CDR Viewer main template. The typical file path is:
Add the include at the top of the file:
Step 3: Add the SIP VAULT Column
Find the CDR table header row and add a new column:
Find the CDR table body row and add the button cell:
Step 4: Verify
Reload the CDR Viewer page. Each CDR row should now have a "SIP VAULT" button that opens the call data in a new tab.
Custom CDR Viewer Integration
For CDR Viewer applications built with any web framework, implement the following algorithm.
Algorithm
function generate_sipvault_url(call_id, customer_id, secret, base_url):
# 1. Hash the Call-ID for the URL path (first 16 hex chars of SHA-256)
call_hash = SHA256(call_id).hex()[:16]
# 2. Get the current hour bucket (Unix timestamp / 3600, integer division)
ts_hour = unix_timestamp() / 3600
# 3. Build the HMAC payload
payload = "{call_id}:{ts_hour}"
# 4. Compute HMAC-SHA256 signature, truncate to 32 hex chars
sig = HMAC_SHA256(key=secret, message=payload).hex()[:32]
# 5. Build the URL
url = "{base_url}/call/{call_hash}?cid={customer_id}&callid={call_id}&ts={ts_hour}&sig={sig}"
return url
Requirements
call_id: The raw SIP Call-ID header value (string)customer_id: Your SIP VAULT customer identifier (string)secret: The HMAC secret matchingSIPVAULT_HMAC_SECRETin/etc/sipvault/api.env(string)base_url: Your SIP VAULT dashboard URL, e.g.,https://sipvault.sippulse.com.br(string)
Important Notes
- No
call_dateis needed — the API scans S3 to find the call automatically - The
ts_hourisunix_timestamp / 3600(integer division), not the raw timestamp - Tokens are valid for ±1 hour relative to the hour bucket they were generated in
- The SHA-256 hash uses only the first 16 hex characters (8 bytes)
- The HMAC signature is truncated to the first 32 hex characters
URL Parameters Explained
| Parameter | Source | Purpose |
|---|---|---|
cid |
Your SIP VAULT account | Identifies the customer for bucket lookup and tenant isolation |
callid |
SIP Call-ID from CDR | Used to locate the call data in S3; also covered by the HMAC signature |
ts |
unix_timestamp / 3600 at generation time |
Hour bucket; determines token validity window (±1 hour) |
sig |
First 32 hex chars of HMAC-SHA256("{callid}:{ts}", secret) |
Proves the link was generated by someone with the HMAC secret |
Token Lifetime
Tokens are valid within a ±1 hour window of the hour bucket in which they were generated:
- A token generated at 14:37 has
ts_hour = 14(UTC hours since epoch) - It remains valid until
ts_hour = 16(i.e., until 16:00 UTC that day) - To extend the session, the operator must return to the CDR Viewer and click the link again
Testing the Integration
Step 1: Generate a Test Link
Use the CLI to generate a link:
PHP:
Python:
Node.js:
All three should produce URLs in this format:
https://sipvault.sippulse.com.br/call/XXXXXXXXXXXXXXXX?cid=sippulse&callid=test-call-id-123&ts=NNNNNN&sig=HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
Step 2: Verify Link Validity
Open the generated URL in a browser. You should see:
- If the call exists in S3: The dashboard loads with call data
- If the call does NOT exist in S3: The dashboard shows "Call not found" (expected for a test Call-ID)
- If the signature is invalid: HTTP 401 error
Step 3: Verify Expiry
Tokens expire after ±1 hour. To test expiry quickly, generate a link and wait until the next full hour boundary plus one hour. The URL should then return HTTP 401 with "invalid signature".
Step 4: Cross-Language Verification
Generate URLs with the same Call-ID using all three language implementations. The call_hash in the URL path should be identical across all three. The ts and sig values will differ if generated at different times, but all should be accepted by the API when fresh.
Troubleshooting
401 Unauthorized: "missing signature"
The request did not include a sig query parameter. Ensure the link generator is adding &sig=... to the URL.
401 Unauthorized: "invalid signature"
The HMAC signature does not match. Check:
- The
SIPVAULT_HMAC_SECRETin your link generator matches the value in/etc/sipvault/api.envexactly - There are no trailing whitespace characters or newlines in either the generator constant or the environment variable
- The
callidvalue is URL-decoded correctly when passed to HMAC verification
401 Unauthorized: "ts expired" or token rejected
The ts hour bucket is outside the ±1 hour window. This happens when:
- The operator waited more than 2 hours before clicking the link
- The server clocks are out of sync between the CDR Viewer server and the SIP VAULT server
Fix: Ensure NTP is running on both servers (chronyc tracking).
"Call not found" on the Dashboard (404)
The token is valid, but no data exists in S3 for this Call-ID. Check:
- The
callidis the complete SIP Call-ID header value (not a CDR record ID or other identifier) - The agent was running and connected to the server at the time of the call
- The call completed (BYE/CANCEL) so that the server session transitioned to COMPLETE and wrote data to S3
- The call is not older than 7 days (the API scans the last 7 days only)
403 Forbidden: "call_hash does not match token"
The call_hash in the URL path does not match the SHA-256 hash of the callid parameter. This means the link was constructed incorrectly. Verify:
The result should match the hash in the URL path.
Browser Shows Raw JSON or Error
Ensure the dashboard SPA is correctly built and deployed at /opt/sipvault/dashboard/. The nginx configuration must serve index.html as the fallback for all routes: