> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ipbox.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Exemplo de integração: webphone + CRM

> Passo a passo para conectar um webphone ao IPBoX e acompanhar as ligações no seu CRM usando a API e os webhooks

Este guia mostra, de ponta a ponta, como integrar o IPBoX a um sistema
externo — por exemplo, o **CRM da sua empresa**:

* o agente atende e faz ligações pelo **webphone**, dentro da tela do seu CRM;
* o **CRM é avisado de cada ligação** (webhooks) e consulta/grava os dados do
  cliente atendido, identificando o prospect pelo **código** que a sua empresa
  cadastrou no IPBoX;
* o **CRM também comanda o IPBoX** (API), por exemplo para discar um número com
  um clique.

> Os exemplos usam apenas requisições HTTP/JSON, então valem para qualquer
> linguagem. O código do receptor de webhook aparece em **Node.js** e **PHP**
> só como referência. Você também não precisa usar o SIP.js: qualquer cliente
> SIP com WebSocket seguro serve.

## Visão geral

```mermaid theme={null}
sequenceDiagram
    participant Agente as Agente (tela do CRM)
    participant CRM as Backend do CRM
    participant IPBoX as IPBoX

    Note over Agente,IPBoX: 1. Conectar o webphone
    Agente->>CRM: Abre a tela de atendimento
    CRM->>IPBoX: POST /ipbox/api/getUserWssCredentials
    IPBoX-->>CRM: url, ramal, pass
    CRM-->>Agente: url, ramal, pass
    Agente->>IPBoX: Registro SIP via WSS (webphone online)

    Note over Agente,IPBoX: 2. Ligação (entrante ou sainte)
    IPBoX->>CRM: Webhook ligacao.recebida / tocando / atendida
    IPBoX->>CRM: Webhook agente.busy (agente na ligação, com o código do prospect)
    CRM->>CRM: Busca o prospect pelo código (ou o cliente pelo telefone)
    CRM-->>Agente: Abre a ficha do cliente (screen pop)

    Note over Agente,IPBoX: 3. Fim da ligação
    IPBoX->>CRM: Webhook ligacao.encerrada (duração, resultado, gravação)
    CRM->>CRM: Grava o histórico da ligação
```

São três peças:

| Peça | Para quê | Onde está documentada |
| - | - | - |
| API `getUserWssCredentials` | Obter as credenciais para o webphone se registrar | [Referência da API](https://docs.ipbox.com.br/api-reference) |
| Webhooks `ligacao.*` e `agente.busy` | Avisar o CRM de cada etapa da ligação (`ligacao.*`) e de quando o agente entra nela, com o `codigo` do prospect (`agente.busy`) | [Eventos de ligação](https://docs.ipbox.com.br/webhooks/referencia-tecnica.md#eventos-de-ligação) e [campos do `agente.busy`](https://docs.ipbox.com.br/webhooks/referencia-tecnica#campos-extras-do-agentebusy) |
| Demais endpoints da API | O CRM comandar o IPBoX (discar, desligar, classificar) | [Referência da API](https://docs.ipbox.com.br/api-reference) |

## Antes de começar

Para seguir este exemplo, confirme que você já tem os três itens abaixo:

* Um **token da API** — veja [Primeiros passos com a API](https://docs.ipbox.com.br/guides/primeiros-passos-api.md).
* O recurso de **WebSocket** ativo no IPBoX. Ele vem habilitado por padrão; se a
  API responder `Websocket desativado`, solicite a ativação ao suporte.
* Uma **URL HTTPS pública** no seu backend para receber os webhooks.

## Passo 1 — Obter as credenciais do webphone

O backend do CRM pede ao IPBoX as credenciais do agente:

```bash theme={null}
curl -X POST "https://SEU_DOMINIO_IPBOX:PORTA/ipbox/api/getUserWssCredentials" \
  -H "Authorization: SEU_TOKEN_AQUI" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "user=agente08&senha=SENHA_DO_AGENTE"
```

Resposta:

```json theme={null}
{
  "status": "success",
  "data": {
    "url": "wss://servidor.ipboxcloud.com.br:8089/ws",
    "host": "servidor.ipboxcloud.com.br:8089",
    "ramal": 2000,
    "pass": "senha123",
    "ws_port": 8089
  }
}
```

| Campo | Como usar no webphone |
| - | - |
| `url` | Endereço do servidor WebSocket seguro (WSS) a que o webphone se conecta |
| `ramal` | Usuário SIP (e usuário de autenticação) |
| `pass` | Senha SIP |
| `host` | Domínio do registro SIP — use o valor **sem a porta** |

> [!WARNING]
>
> Chame essa API sempre do **backend** do CRM. Nunca coloque o token da API no
> código do navegador. Devolva ao navegador apenas `url`, `ramal`, `pass` e o
> domínio, e só para o agente que está logado no CRM.

## Passo 2 — Conectar o webphone

Para o IPBoX, o webphone é um cliente SIP comum. Qualquer biblioteca ou
softphone que suporte:

* registro SIP sobre **WebSocket seguro (WSS)**;
* mídia **WebRTC** (ICE e DTLS-SRTP);
* pelo menos um dos codecs `opus` ou `ulaw`;

pode usar as credenciais do passo anterior. Como **exemplo**, com o SIP.js:

```js theme={null}
import { UserAgent, Registerer } from "sip.js";

// cred = { url, ramal, pass, host } devolvido pelo backend do CRM
const dominio = cred.host.split(":")[0];

const userAgent = new UserAgent({
  uri: UserAgent.makeURI(`sip:${cred.ramal}@${dominio}`),
  transportOptions: { server: cred.url },
  authorizationUsername: String(cred.ramal),
  authorizationPassword: cred.pass,
});

await userAgent.start();
await new Registerer(userAgent).register();
// O webphone está registrado: já pode receber e fazer ligações.
```

## Passo 3 — Deixar o agente disponível

Para receber ligações de **fila** ou do **discador**, o agente precisa estar
logado no IPBoX e fora de pausa. Chame, a partir do backend:

```bash theme={null}
# 1. Login (use o ramal do passo 1)
curl -X POST "https://SEU_DOMINIO_IPBOX:PORTA/ipbox/api/login" \
  -H "Authorization: SEU_TOKEN_AQUI" \
  -d "user=agente08&senha=SENHA_DO_AGENTE&time=3&ramal=2000"

# 2. Sair da pausa (depois do login o agente fica em pausa)
curl -X POST "https://SEU_DOMINIO_IPBOX:PORTA/ipbox/api/pauseout" \
  -H "Authorization: SEU_TOKEN_AQUI" \
  -d "user=agente08"
```

> [!WARNING]
>
> Se o parâmetro **`DESLOGA_AGENTE_PAINEL_OFF`** estiver ativo no IPBoX ("Deslogar
> agentes com painel do agente off"), o agente que fez login pela API é
> **deslogado automaticamente**. O IPBoX só considera o painel "ligado" enquanto
> o painel do agente do IPBoX está aberto e enviando sinais de vida; quem entra
> só pela API e pelo seu webphone não tem painel aberto, e passa a ser deslogado
> depois de cerca de 2 minutos sem esse sinal. Antes de colocar a integração em
> produção, peça ao suporte para **desativar esse parâmetro**.

Ao encerrar o expediente, use `logout`. Para pausas (almoço etc.), `pausein` e
`pauseout`. Os parâmetros completos estão na [Referência da API](https://docs.ipbox.com.br/api-reference).

## Passo 4 — Cadastrar o webhook

Em **IPBoX > Integrações > Webhooks**, cadastre a URL do seu backend (por
exemplo `https://crm.suaempresa.com.br/webhook-ipbox`), defina um **Secret** e
marque os eventos que o CRM vai usar:

| Evento | Para que o CRM usa |
| - | - |
| `agente.busy` | **Quando o agente entra na ligação.** Traz o `codigo` do prospect (o identificador que você cadastrou no IPBoX), além de `login`, `ramal` e `numero`. É com ele que o CRM encontra o prospect |
| `ligacao.recebida` | Saber que uma entrante chegou e buscar o cliente pelo telefone |
| `ligacao.tocando` | Abrir a ficha do cliente para o agente cujo ramal está tocando |
| `ligacao.atendida` / `ligacao.conectada` | Marcar o início da conversa |
| `ligacao.falha` / `ligacao.abandonada` | Registrar tentativa sem contato |
| `ligacao.encerrada` | Gravar o histórico da ligação |

O `agente.busy` é um evento de **agente** (não de ligação): ele aparece no
mesmo cadastro, no grupo de eventos de agente. Seus campos estão descritos em
[campos extras do `agente.busy`](https://docs.ipbox.com.br/webhooks/referencia-tecnica#campos-extras-do-agentebusy).

Detalhes do cadastro em [Webhooks](https://docs.ipbox.com.br/guides/webhooks.md).

## Passo 5 — Receber os eventos no CRM

O receptor precisa fazer quatro coisas:

1. **Validar** o header `X-Webhook-Secret`.
2. **Responder `200` na hora** e processar depois (respostas lentas ou com erro
   geram reenvio).
3. **Ignorar duplicados** usando o `evento_id` (o mesmo evento pode chegar mais
   de uma vez).
4. **Respeitar a ordem** usando a `sequencia` da ligação (`ligacao_id`): se
   chegar um evento com sequência menor ou igual à última já aplicada, ele é
   antigo e deve ser ignorado.

Os itens 3 e 4 valem para os eventos `ligacao.*`. Os eventos de **agente**
(como o `agente.busy`) não têm `evento_id`, `ligacao_id` nem `sequencia` e são
enviados uma única vez, sem reenvio — então trate-os separadamente, como nos
exemplos abaixo.

```js Node.js (Express) theme={null}
const express = require("express");
const app = express();

const SECRET = process.env.IPBOX_WEBHOOK_SECRET;
const eventosVistos = new Set();       // produção: tabela/Redis com expiração
const ultimaSequencia = new Map();     // produção: coluna na tabela de ligações

app.post("/webhook-ipbox", express.json(), (req, res) => {
  if (req.get("X-Webhook-Secret") !== SECRET) return res.sendStatus(401);
  res.sendStatus(200); // responde já; processa depois
  setImmediate(() => processar(req.body).catch(console.error));
});

async function processar(ev) {
  if (ev.evento === "agente.busy") return tratarAgenteBusy(ev);
  if (!ev.evento || !ev.evento.startsWith("ligacao.")) return;

  if (eventosVistos.has(ev.evento_id)) return;               // duplicado
  eventosVistos.add(ev.evento_id);

  if ((ultimaSequencia.get(ev.ligacao_id) || 0) >= ev.sequencia) return; // antigo
  ultimaSequencia.set(ev.ligacao_id, ev.sequencia);

  switch (ev.evento) {
    case "ligacao.recebida": {
      // entrante chegou: ainda não há agente (ramal/usuario_id nulos)
      const cliente = await buscarClientePorTelefone(ev.numero_origem);
      await registrarLigacaoEmAndamento(ev.ligacao_id, ev, cliente);
      break;
    }
    case "ligacao.tocando":
    case "ligacao.discando": {
      // agora o evento já traz o agente (usuario_id) que está sendo chamado
      if (ev.usuario_id) {
        const numero = ev.direcao === "sainte" ? ev.numero_destino : ev.numero_origem;
        const cliente = await buscarClientePorTelefone(numero);
        await abrirFichaParaAgente(ev.usuario_id, cliente); // screen pop
      }
      break;
    }
    case "ligacao.atendida":
    case "ligacao.conectada":
      await marcarInicioConversa(ev.ligacao_id, ev.ocorrido_em, ev.usuario_id);
      break;
    case "ligacao.encerrada":
      await gravarHistorico(ev.ligacao_id, {
        resultado: ev.resultado,             // atendida, abandonada, desligada, falha...
        motivo: ev.motivo,
        duracao: ev.duracao,
        duracaoAtendida: ev.duracao_atendida,
        gravacaoUrl: ev.gravacao_url,
        agente: ev.usuario_id,
      });
      break;
  }
}

// O agente entrou na ligação: aqui chega o código do prospect.
async function tratarAgenteBusy(ev) {
  // codigo = identificador do prospect cadastrado no IPBoX (vazio em ligações sem prospect)
  const prospect = ev.codigo
    ? await buscarProspectPorCodigo(ev.codigo)
    : await buscarClientePorTelefone(ev.numero);
  await vincularLigacaoAoAgente(ev.usuario_id, ev.numero, prospect);
  await abrirFichaParaAgente(ev.usuario_id, prospect); // screen pop
}

// As funções abaixo são do SEU CRM:
async function buscarProspectPorCodigo(codigo) { /* SELECT ... WHERE codigo = ? */ }
async function vincularLigacaoAoAgente(usuarioId, numero, prospect) { /* UPDATE ... */ }
async function buscarClientePorTelefone(telefone) { /* SELECT ... */ }
async function registrarLigacaoEmAndamento(ligacaoId, ev, cliente) { /* INSERT ... */ }
async function abrirFichaParaAgente(usuarioId, cliente) { /* envia ao navegador (SSE/WebSocket) */ }
async function marcarInicioConversa(ligacaoId, quando, usuarioId) { /* UPDATE ... */ }
async function gravarHistorico(ligacaoId, dados) { /* UPDATE ... */ }

app.listen(3000);
```

```php PHP theme={null}
<?php
$secret = getenv('IPBOX_WEBHOOK_SECRET');
if (($_SERVER['HTTP_X_WEBHOOK_SECRET'] ?? '') !== $secret) {
    http_response_code(401);
    exit;
}

$ev = json_decode(file_get_contents('php://input'), true);

// Responde 200 já e continua processando
http_response_code(200);
header('Connection: close');
header('Content-Length: 0');
if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
}

// Eventos de agente não têm evento_id/sequencia: tratados à parte.
if (($ev['evento'] ?? '') === 'agente.busy') {
    // codigo = identificador do prospect cadastrado no IPBoX (vazio em ligações sem prospect)
    $prospect = !empty($ev['codigo'])
        ? buscarProspectPorCodigo($ev['codigo'])
        : buscarClientePorTelefone($ev['numero']);
    vincularLigacaoAoAgente($ev['usuario_id'], $ev['numero'], $prospect);
    abrirFichaParaAgente($ev['usuario_id'], $prospect);
    exit;
}

if (!isset($ev['evento']) || strpos($ev['evento'], 'ligacao.') !== 0) {
    exit;
}

// Duplicado ou antigo? (tabela com evento_id único e última sequência por ligação)
if (jaProcessado($ev['evento_id']) || $ev['sequencia'] <= ultimaSequencia($ev['ligacao_id'])) {
    exit;
}
marcarProcessado($ev['evento_id'], $ev['ligacao_id'], $ev['sequencia']);

switch ($ev['evento']) {
    case 'ligacao.recebida':
        $cliente = buscarClientePorTelefone($ev['numero_origem']);
        registrarLigacaoEmAndamento($ev['ligacao_id'], $ev, $cliente);
        break;
    case 'ligacao.tocando':
    case 'ligacao.discando':
        if (!empty($ev['usuario_id'])) {
            $numero = $ev['direcao'] === 'sainte' ? $ev['numero_destino'] : $ev['numero_origem'];
            abrirFichaParaAgente($ev['usuario_id'], buscarClientePorTelefone($numero));
        }
        break;
    case 'ligacao.atendida':
    case 'ligacao.conectada':
        marcarInicioConversa($ev['ligacao_id'], $ev['ocorrido_em'], $ev['usuario_id']);
        break;
    case 'ligacao.encerrada':
        gravarHistorico($ev['ligacao_id'], array(
            'resultado' => $ev['resultado'],
            'motivo' => $ev['motivo'],
            'duracao' => $ev['duracao'],
            'duracaoAtendida' => $ev['duracao_atendida'],
            'gravacaoUrl' => $ev['gravacao_url'] ?? null,
            'agente' => $ev['usuario_id'],
        ));
        break;
}
// As funções acima (jaProcessado, buscarProspectPorCodigo...) são do SEU CRM.
```

### Como o CRM encontra o prospect: o `codigo`

O `codigo` do `agente.busy` é o **identificador do prospect** que a sua empresa
informa ao cadastrá-lo no IPBoX (por exemplo, o ID do cliente no CRM). Ele
permite encontrar o registro exato, sem depender do telefone, que pode ser
compartilhado ou estar desatualizado. Por isso, **o `agente.busy` é o evento
mais importante para abrir a ficha**: ele chega quando o agente efetivamente
entra na ligação.

* Se `codigo` vier vazio, a ligação não está associada a um prospect (por
  exemplo, uma ligação manual pelo webphone ou um número que não está
  cadastrado). Use o `numero` para procurar o cliente.
* Para ligar o `agente.busy` aos eventos `ligacao.*` da mesma ligação, use o
  `usuario_id` do agente e o `numero` do cliente (`numero_origem` ou
  `numero_destino`, conforme a `direcao`).

### Como o CRM sabe quem é o agente

Os eventos trazem o `usuario_id` do IPBoX. Para ligá-lo ao usuário do seu CRM,
guarde esse ID no cadastro do agente. Você obtém os IDs com a API
`listarUsuarios` (campos `id` e `login`).

### Como levar a ficha até a tela do agente

O webhook chega no **backend** do CRM. Para abrir a ficha na tela do agente
(*screen pop*), o backend usa o canal que o seu CRM já tem com o navegador
(WebSocket, SSE, push), endereçado pelo `usuario_id` do evento.

## Passo 6 — Comandar o IPBoX a partir do CRM (opcional)

| Ação no CRM | API | Observação |
| - | - | - |
| Botão "ligar" ao lado do telefone do cliente | `dial` (`user` + `numero`) | Disca o número e entrega a ligação ao agente quando o cliente atende |
| Ligar escolhendo o ramal | `discarParaRamal` (`numero` + `ramal`) | Útil quando o CRM conhece o ramal, mas não o login do agente |
| Botão "desligar" | `desligarLigacao` (`user`) | Desliga a ligação em atendimento |
| Consultar a ligação em andamento | `getLigacaoAtivaByRamal`, `getDadosProspectEmAtendimento` | Para recuperar o estado se o CRM reiniciou |
| Tabular o resultado da ligação | `getResultadoLigacao` + `classificarLigacao` | Para ligações de fila/discador, com agendamento de retorno opcional |
| Baixar a gravação | `gravacao_url` do `ligacao.encerrada`, ou `getGravacao` | |

Os parâmetros completos de cada uma estão na [Referência da API](https://docs.ipbox.com.br/api-reference).

## Exemplos de fluxo

### Cliente liga e cai na fila

| # | Evento | O que o CRM faz |
| - | - | - |
| 1 | `ligacao.recebida` | Busca o cliente por `numero_origem` e cria a ligação "em andamento" |
| 2 | `ligacao.discando` | O IPBoX começou a chamar o agente; já vem `usuario_id` → abre a ficha para ele |
| 3 | `ligacao.tocando` | (opcional) Mostra "chamando" |
| 4 | `ligacao.atendida` | Marca o início da conversa |
| 5 | `ligacao.conectada` | (fila) Confirma o agente e o tempo de espera |
| — | `agente.busy` | Traz o `codigo` do prospect: o CRM localiza o prospect e abre/atualiza a ficha |
| 6 | `ligacao.encerrada` | Grava duração, resultado e `gravacao_url` |

Se o cliente desligar na URA, os eventos são só `ligacao.recebida` e
`ligacao.encerrada` (`resultado: desligada`, `motivo: ura`).

### Agente liga para um cliente pelo webphone

| # | Evento | O que o CRM faz |
| - | - | - |
| 1 | `ligacao.discando` | `direcao: sainte`, `numero_destino` é o cliente; já vem o `usuario_id` do agente |
| 2 | `ligacao.tocando` | — |
| 3 | `ligacao.atendida` | Marca o início da conversa (`atendida_por: destino_externo`) |
| — | `agente.busy` | Se o número estiver cadastrado como prospect, traz o `codigo`; senão vem vazio |
| 4 | `ligacao.encerrada` | Grava o histórico |

Se o cliente não atender, vêm `ligacao.falha` (com `motivo`, por exemplo
`nao_atende` ou `ocupado`) e `ligacao.encerrada`.

## Cuidados

* **Para identificar o prospect, use o `codigo` do `agente.busy`.** O
  `prospect_id` dos eventos `ligacao.*` é o ID interno do IPBoX e só existe nas
  ligações do discador. Para ligações feitas ou recebidas pelo webphone, o CRM
  identifica o cliente pelo **telefone** (`numero_origem`/`numero_destino`).
* **`usuario_id` e `ramal` podem vir `null`** em `ligacao.recebida` (ainda não
  há agente) e, por alguns segundos, logo depois do login do agente. Não
  dependa deles nesse evento.
* **Reenvio e ordem:** se o seu endpoint falhar, o IPBoX reenvia até 5 vezes
  (5s, 15s, 1min e 5min); por isso eventos podem chegar fora de ordem ou
  repetidos. Veja [Entrega e reenvio](https://docs.ipbox.com.br/webhooks/referencia-tecnica.md#entrega-e-reenvio-dos-eventos-de-ligação).
* **Segurança:** o `X-Webhook-Secret` é um segredo compartilhado, não uma
  assinatura do corpo. Use sempre **HTTPS**.
* **Teste antes:** aponte o webhook para o [webhook.site](https://webhook.site)
  e faça algumas ligações para ver os payloads reais.

## Próximos passos

* [Referência técnica do Webhook](https://docs.ipbox.com.br/webhooks/referencia-tecnica.md) — todos os
  campos de cada evento.
* [Referência da API](https://docs.ipbox.com.br/api-reference) — todos os endpoints.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.