Pular para conteúdo

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

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:

<?php echo sipvault_link($row['call_id']); ?>

Botão com ícone:

<?php echo sipvault_button($row['call_id']); ?>

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

php generate_link.php "a7a186b1ff8c4cfeaa90a9144ba9cb24"

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

python generate_link.py "a7a186b1ff8c4cfeaa90a9144ba9cb24"

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

node generate_link.js "a7a186b1ff8c4cfeaa90a9144ba9cb24"

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.

cp deploy/cdr-integration/generate_link.php /var/www/opensips-cp/web/tools/cdrviewer/

Passo 2: Editar o Template do CDR Viewer

Edite o template principal do CDR Viewer. O caminho típico do arquivo é:

/var/www/opensips-cp/web/tools/cdrviewer/cdrviewer.main.php

Adicione o include no topo do arquivo:

<?php require_once __DIR__ . '/generate_link.php'; ?>

Passo 3: Adicionar a Coluna SIP VAULT

Encontre a linha de cabeçalho da tabela CDR e adicione uma nova coluna:

<th>SIP VAULT</th>

Encontre a linha do corpo da tabela CDR e adicione a célula com o botão:

<td><?php echo sipvault_button($cdr['callid']); ?></td>

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_date nã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

PHP:

php deploy/cdr-integration/generate_link.php "test-call-id-123"

Python:

python deploy/cdr-integration/generate_link.py "test-call-id-123"

Node.js:

node deploy/cdr-integration/generate_link.js "test-call-id-123"

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

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:

  1. O SIPVAULT_HMAC_SECRET no seu gerador de links corresponde exatamente ao valor em /etc/sipvault/api.env
  2. Não há espaços em branco ou quebras de linha no final da constante ou da variável de ambiente
  3. 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:

  1. O callid é o valor completo do cabeçalho SIP Call-ID (não um ID de registro CDR)
  2. O agente estava rodando e conectado ao servidor no momento da chamada
  3. A chamada foi concluída (BYE/CANCEL) para que a sessão transicionasse para COMPLETE
  4. 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:

echo -n "the-call-id" | sha256sum | cut -c1-16

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:

location / {
    try_files $uri $uri/ /index.html;
}