> For the complete documentation index, see [llms.txt](https://docs.sprinthub.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sprinthub.com/topicos/api/api-voip-webhook.md).

# API - VoIP Webhook

Rota pública usada por centrais/provedores de telefonia externos para enviar chamadas já finalizadas ao SprintHub.

A cada evento recebido, o SprintHub:

* identifica o lead pelo telefone (ou cria um novo lead, se o número for desconhecido);
* registra a chamada no histórico de chamadas (`voip_history`), visível no CRM;
* registra o evento na linha do tempo do lead (`voip.call_incoming` ou `voip.call_outgoing`).

<figure><img src="/files/JCy2iTdVBBLRZMHrx0sF" alt=""><figcaption></figcaption></figure>

***

### Endpoint

```
POST https://api.sprinthub.app/voip/webhook?i=NOME_DO_CLIENTE
Content-Type: application/json
```

Substitua `NOME_DO_CLIENTE` pelo nome da instância (ex: `meucliente`). O parâmetro `?i=` é o que identifica a base de dados do cliente e deve estar sempre presente na URL configurada no provedor.

***

### Autenticação

A autenticação é **opcional** e definida por instância:

| Situação                            | Comportamento                                                         |
| ----------------------------------- | --------------------------------------------------------------------- |
| Cliente **sem** segredo configurado | O endpoint aceita a requisição sem validação adicional                |
| Cliente **com** segredo configurado | É obrigatório enviar o segredo, senão a resposta é `401 unauthorized` |

Quando o segredo estiver ativo, envie-o de uma das duas formas:

```
x-voip-signature: SEU_SEGREDO
```

ou

```
POST /voip/webhook?i=NOME_DO_CLIENTE&secret=SEU_SEGREDO
```

> A configuração do segredo (`voip_webhook_secret`) é feita pela equipe SprintHub na instância do cliente.

***

### Corpo da requisição

O corpo é um envelope com dois campos: `event` e `data`.

```json
{
  "event": "call",
  "data": { ... }
}
```

| Campo   | Tipo   | Obrigatório | Descrição                                     |
| ------- | ------ | ----------- | --------------------------------------------- |
| `event` | string | sim         | Único valor aceito hoje: `"call"`             |
| `data`  | object | sim         | Dados da chamada finalizada (tabela a seguir) |

#### Campos de `data`

| Campo         | Tipo    | Obrigatório | Descrição                                                                       |
| ------------- | ------- | ----------- | ------------------------------------------------------------------------------- |
| `user`        | number  | **sim**     | ID do usuário SprintHub que fez/recebeu a chamada. Precisa existir na instância |
| `leadPhone`   | string  | **sim**     | Telefone do contato, com DDI. Caracteres não numéricos são removidos            |
| `type`        | string  | **sim**     | `INCOMING` (recebida) ou `OUTGOING` (realizada)                                 |
| `status`      | string  | **sim**     | `COMPLETE` (atendida) ou `INCOMPLETE` (não atendida)                            |
| `duration`    | number  | **sim**     | Duração em **segundos**. Use `0` para chamadas não atendidas                    |
| `record`      | boolean | **sim**     | Indica se a chamada foi gravada na central                                      |
| `service`     | string  | **sim**     | Identificador do provedor/central (até 32 caracteres). Ex: `"twilio"`           |
| `lead`        | number  | não         | ID do lead, se já conhecido. Se omitido, o lead é resolvido pelo telefone       |
| `leadName`    | string  | não         | Nome do contato (até 64 caracteres)                                             |
| `userName`    | string  | não         | Nome do operador (até 64 caracteres)                                            |
| `callId`      | string  | não         | ID da chamada no provedor (até 64 caracteres). Usado para conciliação           |
| `description` | string  | não         | Observação livre sobre a chamada (até 1000 caracteres)                          |

> Campos não listados acima são rejeitados: qualquer propriedade extra resulta em `400 invalid_body`.

#### Exemplo — chamada recebida e atendida

```json
{
  "event": "call",
  "data": {
    "user": 42,
    "userName": "Maria Souza",
    "leadPhone": "+55 11 98888-7777",
    "leadName": "João Silva",
    "type": "INCOMING",
    "status": "COMPLETE",
    "duration": 185,
    "record": true,
    "callId": "CA-2f9c81",
    "service": "twilio",
    "description": "Cliente pediu proposta comercial"
  }
}
```

#### Exemplo — chamada realizada e não atendida

```json
{
  "event": "call",
  "data": {
    "user": 42,
    "leadPhone": "5511988887777",
    "type": "OUTGOING",
    "status": "INCOMPLETE",
    "duration": 0,
    "record": false,
    "callId": "CA-2f9c82",
    "service": "twilio"
  }
}
```

#### Exemplo com cURL

```bash
curl -X POST "https://api.sprinthub.app/voip/webhook?i=meucliente" \
  -H "Content-Type: application/json" \
  -H "x-voip-signature: SEU_SEGREDO" \
  -d '{
    "event": "call",
    "data": {
      "user": 42,
      "leadPhone": "5511988887777",
      "type": "INCOMING",
      "status": "COMPLETE",
      "duration": 185,
      "record": true,
      "service": "twilio"
    }
  }'
```

***

### Collection do Postman

Arquivo: `voip_webhook_postman_collection.json` — importe no Postman em *Import > File*.

Antes de rodar, ajuste as variáveis da collection:

| Variável    | Descrição                                                      |
| ----------- | -------------------------------------------------------------- |
| `baseUrl`   | URL da API (ex: `https://api.sprinthub.app`)                   |
| `instance`  | Nome da instância, usado no `?i=`                              |
| `secret`    | Segredo do webhook. Deixe vazio se a instância não usa segredo |
| `userId`    | ID de um usuário existente na instância                        |
| `leadPhone` | Telefone usado nos testes                                      |
| `service`   | Identificador do provedor enviado em `service`                 |
| `leadId`    | Preencha apenas para a requisição *"com lead informado"*       |

A collection tem duas pastas:

* **Sucesso** — chamada recebida atendida, realizada não atendida, payload mínimo, com lead informado e com telefone mascarado. Cada requisição valida `201`/`msg: created` e guarda o ID retornado na variável `historyId`.
* **Erros** — os casos de `400` (campo obrigatório ausente, `event` inválido, enum inválido, campo extra) e o `401` de segredo inválido.

> As requisições da pasta **Sucesso** criam registros reais no histórico de chamadas e podem criar o lead pelo telefone informado. Use uma instância de teste. O `callId` é gerado com `{{$timestamp}}` para cada envio ser único.

***

### O que o SprintHub faz com o evento

1. **Valida o corpo** — formato, tipos e campos obrigatórios.
2. **Normaliza o telefone** — remove tudo que não é dígito de `leadPhone`.
3. **Resolve o lead**:
   * se `lead` foi enviado, ele é usado direto;
   * senão, procura um lead existente pelos campos telefone, celular e WhatsApp (com tratamento do 9º dígito em números brasileiros);
   * se não encontrar, cria um novo lead com origem **VoIP**, usando o nome e a foto do WhatsApp quando o número tiver conta ativa, ou o próprio telefone como nome.
4. **Registra a chamada** no histórico de chamadas e retorna o ID gerado.
5. **Registra na linha do tempo do lead** o evento `voip.call_incoming` ou `voip.call_outgoing`, com operador, telefone, status, duração, `callId` e `service`.

> Se a etapa de resolução/criação do lead falhar, a chamada ainda é registrada — apenas sem vínculo com lead (e, nesse caso, sem evento na linha do tempo).

***

### Respostas

| Código | Corpo                                            | Significado                                                           |
| ------ | ------------------------------------------------ | --------------------------------------------------------------------- |
| `201`  | `{ "msg": "created", "id": 123 }`                | Chamada registrada. `id` é o identificador no histórico de chamadas   |
| `400`  | `{ "msg": "invalid_body", "err": { ... } }`      | Corpo inválido: campo obrigatório ausente, tipo errado ou campo extra |
| `401`  | `{ "msg": "unauthorized" }`                      | Segredo configurado na instância e não enviado (ou enviado incorreto) |
| `500`  | `{ "msg": "failed_voip_webhook", "err": "..." }` | Falha ao gravar a chamada. Pode ser reenviado                         |

O campo `err` do `400` traz o detalhe da validação, útil para depurar a integração:

```json
{
  "msg": "invalid_body",
  "err": {
    "instancePath": "/data",
    "keyword": "required",
    "message": "must have required property 'service'"
  }
}
```

***

### Boas práticas

* **Envie um evento por chamada finalizada**, após o encerramento — o webhook não trata eventos de progresso (ringing, answered, etc.).
* **Não há deduplicação por `callId`**: reenviar o mesmo evento cria um novo registro no histórico. Envie sempre `callId` para permitir conciliação e evite reenvios desnecessários.
* **Reenvie apenas em erro `500` ou timeout**, com espera progressiva entre tentativas. Como a falha pode ocorrer após a gravação da chamada, um reenvio pode gerar registro duplicado — use o `callId` para identificar.
* **`duration` em segundos**, sempre número inteiro.
* **`record: true` apenas sinaliza** que existe gravação na central; o áudio não é enviado nem anexado por este endpoint.
* **Respeite os limites de tamanho** dos campos de texto (`userName`, `leadName` e `callId` até 64; `service` até 32; `description` até 1000). Valores maiores podem ser truncados ou causar erro `500`.
* **Automação:** o gatilho *"recebeu ligação"* (`leadPhoneCall`, evento `received`) reconhece as chamadas `INCOMING` recebidas por este webhook. O filtro por número do operador não se aplica, pois o webhook não envia esse número.
* Este endpoint **não gera consumo de créditos nem transcrição automática** — ele apenas registra o histórico da chamada e o evento no lead.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.sprinthub.com/topicos/api/api-voip-webhook.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
