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

# WebSocket

> Receba mensagens em tempo real por uma conexão WebSocket persistente

O recebimento de mensagens em tempo real usa **WebSocket puro**, com um protocolo próprio de mensagens JSON. Cada mensagem trocada tem um campo `metodo` que identifica a operação.

## Endpoint

```text theme={null}
wss://apiv1.hustapp.com:2083
```

Não há path nem query string — a conexão abre direto na raiz. Note que a porta é a `2083`, diferente da porta HTTPS usada pelas rotas REST.

## Fluxo de conexão

<Steps>
  <Step title="Abra o socket">
    Conecte em `wss://apiv1.hustapp.com:2083`.
  </Step>

  <Step title="Autentique">
    Envie o `metodo: "login"` com seu token e aguarde a confirmação.
  </Step>

  <Step title="Assine os eventos">
    Envie um `adicionarEvento` para cada tipo de evento que quer receber.
  </Step>

  <Step title="Mantenha a conexão viva">
    Envie um `ping` a cada 10 segundos.
  </Step>
</Steps>

## Autenticando

Assim que o socket abrir, envie:

```json theme={null}
{ "metodo": "login", "token": "SEU_TOKEN" }
```

O servidor responde:

```json theme={null}
{ "metodo": "login", "autenticado": true }
```

Se `autenticado` vier `false`, a conexão continua aberta mas nenhum evento será entregue. Verifique o token e reconecte.

## Assinando eventos

Apenas autenticar não basta: você precisa declarar quais eventos quer receber. Envie uma mensagem por evento, depois da confirmação do login.

```json theme={null}
{ "metodo": "adicionarEvento", "evento": "monitorarChat" }
```

| `evento`                  | Entrega                                       |
| ------------------------- | --------------------------------------------- |
| `monitorarChat`           | Mensagens dos chats do usuário dono do token. |
| `monitorarAllChats`       | Mensagens de todos os chats da empresa.       |
| `monitorarStatusConexoes` | Mudanças de status das conexões de WhatsApp.  |

Para cancelar uma assinatura, use `removerEvento` com o mesmo valor de `evento`.

<Warning>
  Sem pelo menos um `adicionarEvento`, a conexão fica aberta e autenticada, mas nenhuma mensagem é entregue.
</Warning>

## Mantendo a conexão viva

Envie um ping a cada 10 segundos:

```json theme={null}
{ "metodo": "ping" }
```

O servidor responde imediatamente:

```json theme={null}
{ "metodo": "pong" }
```

Use o pong para monitorar a saúde da conexão: se os pongs pararem de chegar, o socket provavelmente está morto mesmo que ainda não tenha emitido um evento de fechamento. Nesse caso, force a reconexão.

## Recebendo uma mensagem

Eventos de mensagem chegam como um objeto JSON **sem** o campo `metodo`. Identifique-os pela presença de `id_mensagem_whatsapp`.

```json theme={null}
{
  "id_mensagem_whatsapp": "3EB0C767D26B8F1A2C11",
  "id_mensagem": 255678,
  "data": "2026-09-01T14:32:10.000Z",
  "body": "Olá, preciso de ajuda",
  "tipo": "chat",
  "flag_enviado": false,
  "id_whatsapp_conexao_fk": 919872,
  "id_departamento": 169070,
  "id_contato_fk": 111211,
  "id_chamado": 223924
}
```

| Campo                    | Tipo      | Descrição                                                                                     |
| ------------------------ | --------- | --------------------------------------------------------------------------------------------- |
| `id_mensagem_whatsapp`   | `string`  | ID da mensagem no WhatsApp.                                                                   |
| `id_mensagem`            | `integer` | ID da mensagem no Hust. Use para buscar mídia.                                                |
| `data`                   | `string`  | Data e hora da mensagem.                                                                      |
| `body`                   | `string`  | Texto da mensagem. Ausente em mensagens de mídia.                                             |
| `tipo`                   | `string`  | Tipo do conteúdo. `chat` para texto, `ptt` para áudio gravado, `audio` para arquivo de áudio. |
| `flag_enviado`           | `boolean` | `true` quando a mensagem saiu da empresa; `false` quando veio do contato.                     |
| `id_whatsapp_conexao_fk` | `integer` | ID da conexão que recebeu a mensagem.                                                         |
| `id_departamento`        | `integer` | ID do departamento do atendimento.                                                            |
| `id_contato_fk`          | `integer` | ID do contato remetente.                                                                      |
| `id_chamado`             | `integer` | ID do atendimento ao qual a mensagem pertence.                                                |

<Tip>
  O evento entrega apenas os IDs de conexão, contato e departamento. Mantenha um cache local dessas entidades ao conectar, em vez de consultar a API a cada mensagem recebida.
</Tip>

## Baixando mídia

Mensagens de mídia não trazem o conteúdo binário no evento. Busque separadamente com o `id_mensagem`:

```text theme={null}
GET https://apiv1.hustapp.com/v1/chat/getMediaFromMessageID
```

| Parâmetro     | Tipo      | Descrição                           |
| ------------- | --------- | ----------------------------------- |
| `token`       | `string`  | Seu token de autenticação.          |
| `id_mensagem` | `integer` | O `id_mensagem` recebido no evento. |

A resposta é o binário do arquivo. O formato vem no cabeçalho `Content-Type` da resposta.

## Reconexão

Se a conexão cair, reconecte e refaça o fluxo completo: autenticar e assinar os eventos novamente. As assinaturas não persistem entre conexões.

<Warning>
  Não há reenvio de eventos perdidos. Mensagens que chegarem enquanto sua aplicação estiver desconectada não serão entregues na reconexão.

  Se a sua integração não pode perder mensagens, reconcilie após cada reconexão consultando os atendimentos do período pela API REST, ou use [webhook](/chat/webhook), que tem retentativa.
</Warning>

## Exemplo

<CodeGroup>
  ```javascript Node.js theme={null}
  import WebSocket from 'ws';

  const ws = new WebSocket('wss://apiv1.hustapp.com:2083');

  ws.on('open', () => {
    ws.send(JSON.stringify({ metodo: 'login', token: process.env.HUST_TOKEN }));

    setInterval(() => {
      if (ws.readyState === ws.OPEN) ws.send(JSON.stringify({ metodo: 'ping' }));
    }, 10_000);
  });

  ws.on('message', (raw) => {
    const payload = JSON.parse(raw.toString());

    // Confirmação do login: assina os eventos
    if (payload.metodo === 'login') {
      if (!payload.autenticado) throw new Error('Falha na autenticação');

      ws.send(JSON.stringify({ metodo: 'adicionarEvento', evento: 'monitorarChat' }));
      return;
    }

    if (payload.metodo === 'pong') return;

    // Mensagem recebida
    if (payload.id_mensagem_whatsapp) {
      if (payload.flag_enviado) return; // ignora as que a própria empresa enviou

      console.log(`[${payload.tipo}] contato ${payload.id_contato_fk}: ${payload.body}`);
    }
  });

  ws.on('close', () => {
    // Reconecte e refaça login + assinaturas
  });
  ```

  ```python Python theme={null}
  # pip install websocket-client
  import json
  import os
  import threading
  import websocket

  TOKEN = os.environ["HUST_TOKEN"]


  def enviar_pings(ws):
      while True:
          threading.Event().wait(10)
          try:
              ws.send(json.dumps({"metodo": "ping"}))
          except Exception:
              return


  def on_open(ws):
      ws.send(json.dumps({"metodo": "login", "token": TOKEN}))
      threading.Thread(target=enviar_pings, args=(ws,), daemon=True).start()


  def on_message(ws, raw):
      payload = json.loads(raw)

      # Confirmação do login: assina os eventos
      if payload.get("metodo") == "login":
          if not payload.get("autenticado"):
              raise RuntimeError("Falha na autenticação")

          ws.send(json.dumps({"metodo": "adicionarEvento", "evento": "monitorarChat"}))
          return

      if payload.get("metodo") == "pong":
          return

      # Mensagem recebida
      if payload.get("id_mensagem_whatsapp"):
          if payload.get("flag_enviado"):
              return  # ignora as que a própria empresa enviou

          print(f"[{payload['tipo']}] contato {payload['id_contato_fk']}: {payload.get('body')}")


  ws = websocket.WebSocketApp(
      "wss://apiv1.hustapp.com:2083",
      on_open=on_open,
      on_message=on_message,
  )

  # reconnect faz o loop reabrir a conexão automaticamente
  ws.run_forever(reconnect=5)
  ```

  ```php PHP theme={null}
  <?php
  // composer require textalk/websocket
  require 'vendor/autoload.php';

  use WebSocket\Client;

  $client = new Client('wss://apiv1.hustapp.com:2083', ['timeout' => 15]);
  $token = getenv('HUST_TOKEN');

  $client->send(json_encode(['metodo' => 'login', 'token' => $token]));

  $ultimoPing = time();

  while (true) {
      // Ping a cada 10 segundos
      if (time() - $ultimoPing >= 10) {
          $client->send(json_encode(['metodo' => 'ping']));
          $ultimoPing = time();
      }

      try {
          $payload = json_decode($client->receive(), true);
      } catch (\WebSocket\TimeoutException $e) {
          continue;
      }

      // Confirmação do login: assina os eventos
      if (($payload['metodo'] ?? null) === 'login') {
          if (empty($payload['autenticado'])) {
              throw new RuntimeException('Falha na autenticação');
          }

          $client->send(json_encode([
              'metodo' => 'adicionarEvento',
              'evento' => 'monitorarChat',
          ]));
          continue;
      }

      if (($payload['metodo'] ?? null) === 'pong') {
          continue;
      }

      // Mensagem recebida
      if (!empty($payload['id_mensagem_whatsapp'])) {
          if (!empty($payload['flag_enviado'])) {
              continue; // ignora as que a própria empresa enviou
          }

          printf(
              "[%s] contato %d: %s\n",
              $payload['tipo'],
              $payload['id_contato_fk'],
              $payload['body'] ?? ''
          );
      }
  }
  ```

  ```pascal Delphi theme={null}
  // Requer um componente WebSocket de terceiros (exemplo com sgcWebSockets).
  // A RTL do Delphi não traz cliente WebSocket nativo.
  uses
    sgcWebSocket, System.JSON, System.SysUtils;

  type
    THustClient = class
    private
      FClient: TsgcWebSocketClient;
      FTimer: TTimer;
      procedure OnConnect(Connection: TsgcWSConnection);
      procedure OnMessage(Connection: TsgcWSConnection; const Text: string);
      procedure OnPingTimer(Sender: TObject);
    public
      constructor Create;
    end;

  constructor THustClient.Create;
  begin
    FClient := TsgcWebSocketClient.Create(nil);
    FClient.URL := 'wss://apiv1.hustapp.com:2083';
    FClient.OnConnect := OnConnect;
    FClient.OnMessage := OnMessage;

    FTimer := TTimer.Create(nil);
    FTimer.Interval := 10000; // ping a cada 10 segundos
    FTimer.OnTimer := OnPingTimer;
    FTimer.Enabled := False;

    FClient.Active := True;
  end;

  procedure THustClient.OnConnect(Connection: TsgcWSConnection);
  var
    Login: TJSONObject;
  begin
    Login := TJSONObject.Create;
    try
      Login.AddPair('metodo', 'login');
      Login.AddPair('token', GetEnvironmentVariable('HUST_TOKEN'));
      FClient.WriteData(Login.ToJSON);
    finally
      Login.Free;
    end;

    FTimer.Enabled := True;
  end;

  procedure THustClient.OnPingTimer(Sender: TObject);
  begin
    FClient.WriteData('{"metodo":"ping"}');
  end;

  procedure THustClient.OnMessage(Connection: TsgcWSConnection; const Text: string);
  var
    Payload: TJSONObject;
    Metodo: string;
  begin
    Payload := TJSONObject.ParseJSONValue(Text) as TJSONObject;
    if not Assigned(Payload) then Exit;
    try
      Payload.TryGetValue<string>('metodo', Metodo);

      // Confirmação do login: assina os eventos
      if Metodo = 'login' then
      begin
        if not Payload.GetValue<Boolean>('autenticado') then
          raise Exception.Create('Falha na autenticação');

        FClient.WriteData('{"metodo":"adicionarEvento","evento":"monitorarChat"}');
        Exit;
      end;

      if Metodo = 'pong' then Exit;

      // Mensagem recebida
      if Payload.GetValue('id_mensagem_whatsapp') <> nil then
      begin
        if Payload.GetValue<Boolean>('flag_enviado') then
          Exit; // ignora as que a própria empresa enviou

        Writeln(Format('[%s] contato %d: %s', [
          Payload.GetValue<string>('tipo'),
          Payload.GetValue<Integer>('id_contato_fk'),
          Payload.GetValue<string>('body')
        ]));
      end;
    finally
      Payload.Free;
    end;
  end;
  ```
</CodeGroup>

<Note>
  Manter uma conexão WebSocket exige um processo rodando continuamente. Em PHP, isso significa um script CLI em loop sob supervisor, não uma requisição servida pelo Apache ou Nginx — se a sua stack PHP é web tradicional, o [webhook](/chat/webhook) costuma encaixar melhor.
</Note>
