Guia de Integração CDR do SIP VAULT
Este guia cobre a integração do SIP VAULT com aplicações CDR Viewer para que os operadores possam abrir dados de troubleshooting de chamadas diretamente a partir dos registros CDR com um único clique.
Sumário
- Visão Geral
- Como Funciona
- Integração PHP (OpenSIPS Control Panel)
- Integração Python
- Integração Node.js
- Modificação do Template do CDR Viewer no OpenSIPS CP
- Integração com CDR Viewer Personalizado
- Parâmetros da URL Explicados
- Testando a Integração
- Resolução de Problemas
Visão Geral
O SIP VAULT fornece um mecanismo de "deep link" que permite que aplicações CDR Viewer gerem URLs autenticadas apontando diretamente para os dados de troubleshooting de uma chamada específica. Quando um operador clica no link, o dashboard do SIP VAULT abre com a captura SIP completa, análise de qualidade RTCP e logs do OpenSIPS daquela chamada.
A integração é leve: uma única função que recebe um Call-ID, calcula uma assinatura HMAC-SHA256 com um timestamp e retorna uma URL. Sem chamadas de API, sem callbacks, sem estado de sessão. A data da chamada não precisa ser passada — a API resolve automaticamente a partir do S3.
Como Funciona
1. Operador visualiza a lista de CDR no CDR Viewer (ex: OpenSIPS Control Panel)
2. Cada linha de CDR possui um botão "SIP VAULT"
3. Ao clicar, o servidor do CDR Viewer gera uma URL assinada com HMAC:
a. Faz hash do Call-ID: call_hash = SHA-256(call_id)[:16]
b. Obtém a hora atual: ts_hour = unix_timestamp / 3600 (divisão inteira)
c. Constrói o payload: "{call_id}:{ts_hour}"
d. Assina o payload: sig = HMAC-SHA256(secret, payload).hex()[:32]
e. Constrói a URL: https://sipvault.example.com/call/{call_hash}
?cid={customer_id}&callid={call_id}&ts={ts_hour}&sig={sig}
4. O navegador do usuário abre a URL
5. O dashboard do SIP VAULT valida a assinatura e carrega os dados da chamada
O segredo HMAC usado para assinar deve corresponder ao SIPVAULT_HMAC_SECRET configurado em /etc/sipvault/api.env no servidor SIP VAULT. O segredo nunca sai do lado do servidor.
Os tokens são válidos por ±1 hora a partir da hora em que foram gerados.
Integração PHP (OpenSIPS Control Panel)
Esta é a integração principal para o OpenSIPS Control Panel (OpenSIPS CP).
Instalação
Copie o arquivo deploy/cdr-integration/generate_link.php para a sua instalação do OpenSIPS CP.
Configuração
Edite as constantes no topo do arquivo para corresponder à sua implantação:
define('SIPVAULT_BASE_URL', 'https://sipvault.sippulse.com.br');
define('SIPVAULT_HMAC_SECRET', 'your_hmac_secret_here'); // Deve corresponder ao api.env
define('SIPVAULT_CUSTOMER_ID', 'sippulse');
Código-Fonte Completo
<?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";
}
Uso em Templates
Link simples:
Botão com ícone:
HTML inline:
<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>
Teste via CLI
Integração Python
Para aplicações CDR Viewer construídas com Python (Django, Flask, FastAPI, etc.).
Instalação
Copie deploy/cdr-integration/generate_link.py para o seu projeto.
Configuração
Edite as constantes no topo do arquivo:
SIPVAULT_BASE_URL = "https://sipvault.sippulse.com.br"
SIPVAULT_HMAC_SECRET = "your_hmac_secret_here" # Deve corresponder ao api.env
Código-Fonte Completo
"""
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:
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))
Uso no 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})
Uso no 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)
Teste via CLI
Integração Node.js
Para aplicações CDR Viewer construídas com Node.js (Express, Next.js, etc.).
Instalação
Copie deploy/cdr-integration/generate_link.js para o seu projeto.
Configuração
Defina variáveis de ambiente ou edite os padrões no arquivo:
export SIPVAULT_BASE_URL="https://sipvault.sippulse.com.br"
export SIPVAULT_HMAC_SECRET="your_hmac_secret_here"
export SIPVAULT_CUSTOMER_ID="sippulse"
Código-Fonte Completo
const crypto = require('crypto')
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'
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 }
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)
}
console.log(sipvaultLink(callId, null, process.argv[3] || SIPVAULT_CUSTOMER_ID))
}
Uso no 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 })
})
Teste via CLI
Modificação do Template do CDR Viewer no OpenSIPS CP
Para adicionar o botão SIP VAULT ao CDR Viewer do OpenSIPS Control Panel, modifique o template da lista de CDR.
Passo 1: Copiar o Gerador de Links
Passo 2: Editar o Template do CDR Viewer
Edite o template principal do CDR Viewer. O caminho típico do arquivo é:
Adicione o include no topo do arquivo:
Passo 3: Adicionar a Coluna SIP VAULT
Encontre a linha de cabeçalho da tabela CDR e adicione uma nova coluna:
Encontre a linha do corpo da tabela CDR e adicione a célula com o botão:
Passo 4: Verificar
Recarregue a página do CDR Viewer. Cada linha de CDR deve agora ter um botão "SIP VAULT" que abre os dados da chamada em uma nova aba.
Integração com CDR Viewer Personalizado
Para aplicações CDR Viewer construídas com qualquer framework web, implemente o seguinte algoritmo.
Algoritmo
function generate_sipvault_url(call_id, customer_id, secret, base_url):
# 1. Hash do Call-ID para o caminho da URL (primeiros 16 hex chars do SHA-256)
call_hash = SHA256(call_id).hex()[:16]
# 2. Obter o bucket de hora atual (divisão inteira)
ts_hour = unix_timestamp() / 3600
# 3. Construir o payload HMAC
payload = "{call_id}:{ts_hour}"
# 4. Calcular assinatura HMAC-SHA256, truncada para 32 hex chars
sig = HMAC_SHA256(key=secret, message=payload).hex()[:32]
# 5. Construir a URL
url = "{base_url}/call/{call_hash}?cid={customer_id}&callid={call_id}&ts={ts_hour}&sig={sig}"
return url
Notas Importantes
- A
call_datenão é necessária — a API escaneia o S3 para encontrar a chamada automaticamente - O
ts_houréunix_timestamp / 3600(divisão inteira), não o timestamp bruto - Os tokens são válidos por ±1 hora em relação ao bucket de hora em que foram gerados
- O hash SHA-256 usa apenas os primeiros 16 caracteres hex (8 bytes)
- A assinatura HMAC é truncada para os primeiros 32 caracteres hex
Parâmetros da URL Explicados
| Parâmetro | Fonte | Finalidade |
|---|---|---|
cid |
Sua conta SIP VAULT | Identifica o cliente para lookup do bucket e isolamento de tenant |
callid |
SIP Call-ID do CDR | Usado para localizar os dados da chamada no S3; coberto pela assinatura HMAC |
ts |
unix_timestamp / 3600 no momento da geração |
Bucket de hora; determina a janela de validade do token (±1 hora) |
sig |
Primeiros 32 hex do HMAC-SHA256("{callid}:{ts}", secret) |
Prova que o link foi gerado por alguém com o segredo HMAC |
Validade do Token
Os tokens são válidos dentro de uma janela de ±1 hora do bucket de hora em que foram gerados:
- Um token gerado às 14:37 tem
ts_hour = 14(horas UTC desde a época Unix) - Permanece válido até
ts_hour = 16(ou seja, até as 16:00 UTC) - Para estender a sessão, o operador deve voltar ao CDR Viewer e clicar no link novamente
Testando a Integração
Passo 1: Gerar um Link de Teste
PHP:
Python:
Node.js:
Os três devem produzir URLs neste formato:
https://sipvault.sippulse.com.br/call/XXXXXXXXXXXXXXXX?cid=sippulse&callid=test-call-id-123&ts=NNNNNN&sig=HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
Passo 2: Verificar a Validade do Link
Abra a URL gerada em um navegador. Você deve ver:
- Se a chamada existe no S3: O dashboard carrega com os dados da chamada
- Se a chamada NÃO existe no S3: O dashboard mostra "Chamada não encontrada" (esperado para um Call-ID de teste)
- Se a assinatura for inválida: Erro HTTP 401
Resolução de Problemas
401 Unauthorized: "missing signature"
A requisição não incluiu o parâmetro sig. Certifique-se de que o gerador de links está adicionando &sig=... à URL.
401 Unauthorized: "invalid signature"
A assinatura HMAC não corresponde. Verifique:
- O
SIPVAULT_HMAC_SECRETno seu gerador de links corresponde exatamente ao valor em/etc/sipvault/api.env - Não há espaços em branco ou quebras de linha no final da constante ou da variável de ambiente
- O valor
callidé decodificado corretamente antes de passar para a verificação HMAC
401 Unauthorized: token expirado
O bucket de hora ts está fora da janela de ±1 hora. Isso acontece quando:
- O operador esperou mais de 2 horas antes de clicar no link
- Os relógios dos servidores estão dessincronizados entre o servidor do CDR Viewer e o servidor SIP VAULT
Solução: Certifique-se de que o NTP está rodando em ambos os servidores (chronyc tracking).
"Chamada não encontrada" no Dashboard (404)
O token é válido, mas não existem dados no S3 para este Call-ID. Verifique:
- O
callidé o valor completo do cabeçalho SIP Call-ID (não um ID de registro CDR) - O agente estava rodando e conectado ao servidor no momento da chamada
- A chamada foi concluída (BYE/CANCEL) para que a sessão transicionasse para COMPLETE
- A chamada não é mais antiga que 7 dias (a API escaneia apenas os últimos 7 dias)
403 Forbidden: "call_hash does not match token"
O call_hash no caminho da URL não corresponde ao hash SHA-256 do parâmetro callid. Verifique:
O resultado deve corresponder ao hash no caminho da URL.
Browser Exibe JSON Bruto ou Erro
Certifique-se de que o SPA do dashboard está corretamente compilado e implantado em /opt/sipvault/dashboard/. A configuração nginx deve servir index.html como fallback para todas as rotas: