# Sincronização de histórico

**Evento:** `history` · **Transporte:** POST para o seu receptor

Receba lotes de dados processados durante sincronizações e recuperações de histórico.

## Variações do payload

`EventType` permanece `history`. O campo `event` identifica o conteúdo do lote: por exemplo `messages`, `chats`, `calls`, `labels` ou `chat_labels`. Leia o array correspondente; não suponha que todos os arrays existam no mesmo evento.

Sincronizações de etiquetas podem acrescentar `history_type`, `full_sync`, `summary` e `chunk`. Esses metadados não são universais para todo histórico.

## Identificação da origem

`request_id` identifica de onde o lote veio. No histórico enviado após ler o QR code ou o código de pareamento, todos os lotes (inclusive o `status` de conclusão) trazem o mesmo `request_id`, gerado a cada pareamento e devolvido em `POST /instance/connect` antes da leitura do código e também informado em `GET /instance/history-sync/status`. Em `POST /labels/refresh`, os lotes `labels` e `chat_labels` trazem o `request_id` devolvido na resposta da requisição. Nas respostas de `POST /message/history-sync`, o `request_id` do pedido vem em cada mensagem e no topo dos lotes `messages` e `chats` quando todo o lote pertence ao mesmo pedido; lotes mistos podem omiti-lo no topo.

## Resultado de `POST /message/history-sync`

Cada pedido termina com um único lote `event: "status"` com o `request_id` do pedido e os campos `status`, `has_more`, `history_access`, `received_messages` e `next_request`. Quando o celular indica mais mensagens, mas não entrega uma página que avance, `status` vem `blocked`, com `success: false` e o motivo em `error`, sem `next_request`: não ofereça "carregar mais" com o mesmo pedido. Reenvie `next_request` para pedir as mensagens anteriores; `null` indica que não há uma próxima requisição utilizável; isso só comprova o início da conversa quando `has_more` é `false`. `has_more: false` indica o início da conversa; `null` indica que não foi confirmado. Mensagens que chegarem depois de um `timeout`, até cerca de 5 minutos após o pedido, vêm com o mesmo `request_id`, sem novo `status`; depois disso chegam sem `request_id`.

## Conversas individuais e LID

Em conversas individuais, `chatid` vem pelo número da conversa (`@s.whatsapp.net`), mesmo quando o celular devolve o histórico pelo LID; o LID fica em `chatlid`. Assim as mensagens recuperadas aparecem em `POST /message/find` pela mesma conversa das mensagens ao vivo.

## Como processar

Faça upsert pelos IDs, aceite vários lotes e tolere sobreposição com eventos ao vivo. O recebimento de um lote não comprova que todo o histórico do WhatsApp foi sincronizado. Quando `chunk.has_more` existir, ele descreve aquela sequência de chunks.

## Sequências e conclusão

Cada origem de lotes recebe um `batchChunkOrder`: o histórico inicial, cada parte que o WhatsApp envia depois dele e cada resposta de `POST /message/history-sync`. Chats, mensagens e ligações da mesma origem compartilham o número. Identifique uma sequência por `batchChunkOrder` + `event` e acompanhe seus lotes com `batchNumber` e `batchTotal`. Lotes de sequências diferentes podem chegar intercalados.

Lotes com dados chegam com `batchHistoryStatus: "loading"`. O fim é informado por um lote sem dados, com `event: "status"` e `batchHistoryStatus: "complete"`; seu `batchChunkOrder` é o da última sequência enviada. No histórico do pareamento, ele sai quando o WhatsApp indica o fim da sincronização completa ou após cerca de 2 minutos sem novas partes do histórico. Quando o pareamento pede só o histórico normal (`history_days` ausente, fora de 0–365 ou de 1 a 90 em `POST /instance/connect`), o WhatsApp não envia a sincronização completa, e o `complete` sai por esse prazo. Se partes atrasadas chegarem depois, um novo `complete` é enviado: considere o de maior `batchChunkOrder`. A numeração recomeça em 1 a cada pareamento.

Um novo pareamento pode reenviar mensagens já recebidas. Deduplique por `messageid` e faça upsert pelos IDs: lotes também podem se sobrepor a eventos recebidos ao vivo.

Nos pedidos manuais, o `status` final tem uma sequência própria e `batchHistoryStatus: "complete"`, inclusive quando `status: "timeout"`. Esse marcador encerra o pedido; só `has_more: false` confirma o início da conversa. `completed` aguarda o processamento dos chunks em andamento do chat. Um timeout pode ser parcial, e mensagens atrasadas ainda podem chegar. O status manual é um lote único, com `batchNumber: 1` e `batchTotal: 1`. Com `fallbackMode: "try_alternates"`, o celular pode responder igual ao pedido pelo número e ao pedido pelo LID; a resposta repetida é descartada e não reenvia chats nem mensagens. Uma resposta com mensagens novas ainda pode chegar depois do `status`, com o mesmo `request_id`.


## Habilitar este evento

Adicione `history` à lista `events` do webhook da instância. Preserve os outros eventos de que sua integração precisa.

```json
{
  "enabled": true,
  "url": "https://seu-sistema.example/hooks/whatsapp",
  "events": [
    "history"
  ]
}
```

Envie essa configuração para [POST /webhook](/reference/updateWebhook.md), usando o header `token`. O JSON acima configura a assinatura; os exemplos de entrega abaixo são o que seu servidor recebe.

## Campos do payload

O envelope comum vem na raiz. Campos condicionais podem estar ausentes ou nulos conforme o evento; não use a tabela como obrigação de presença de todos os campos.

| Campo | Tipo | Significado |
|---|---|---|
| `EventType` | string | **Obrigatório.** Tipo de evento, por exemplo connection ou messages. |
| `owner` | string | **Obrigatório.** Consulte o objeto ou exemplo correspondente. |
| `token` | string | **Obrigatório.** Token da instância; dado sensível. |
| `BaseUrl` | string | **Obrigatório.** Consulte o objeto ou exemplo correspondente. |
| `instanceName` | string | Consulte o objeto ou exemplo correspondente. |
| `event` | string | Conteúdo do lote: messages, chats, calls, labels, chat_labels ou status (conclusão do histórico). |
| `request_id` | string | Origem do lote: pareamento, recarga de etiquetas ou pedido manual. Em lotes messages e chats de pedidos manuais, só aparece no topo quando todo o lote pertence ao mesmo pedido; consulte também messages[].request_id. |
| `request_chat` | string | Em `status` de `POST /message/history-sync`: chat do pedido. |
| `status` | string | Resultado do pedido manual: completed quando o WhatsApp confirma sua conclusão; timeout após 2 minutos sem confirmação; blocked quando o celular indica mais mensagens, mas não entregou uma página que avance (o motivo vem em `error`). Não comprova que toda a conversa foi recuperada. |
| `success` | boolean | Em `status` de `POST /message/history-sync`: só aparece, com `false`, junto de `error`. Sempre presente com `status: "blocked"`; num `timeout`, aparece quando o próximo pedido não pôde ser montado. |
| `error` | string | Em `status` de `POST /message/history-sync`: por que não há `next_request`. `pagination_no_progress`: a partir da mesma âncora, o celular não entregou uma página que avance nem no maior `count` (a cada página sem avanço, o `next_request` dobra o `count`, até 100): veio vazia, sem mensagem que sirva de âncora ou repetida. É o fim do que o celular entrega a partir dessa âncora, mas o WhatsApp não confirma que é o início da conversa; repetir o pedido dá o mesmo resultado; `anchor_unavailable`: a mensagem de referência não está mais disponível, peça com outra; `history_access_unavailable`: o restante não está acessível; `cursor_lookup_failed`: falha ao montar o próximo pedido, tente de novo. |
| `has_more` | boolean / null | Em `status` de `POST /message/history-sync`: o que o celular declarou. `true` há mais mensagens no celular, `false` chegou ao início da conversa, `null` não foi confirmado. Alguns aparelhos declaram `true` em toda resposta sob demanda, inclusive na vazia: com eles, a paginação termina em `blocked` com `pagination_no_progress`, nunca em `has_more: false`. |
| `history_access` | string | Em `status` de `POST /message/history-sync`: `unavailable` quando há mais mensagens, mas sem acesso a elas. |
| `received_messages` | integer | Em `status` de `POST /message/history-sync`: mensagens recebidas para o pedido. |
| `next_request` | object / null | Corpo pronto para pedir mensagens anteriores. null indica que não há próxima requisição utilizável; só has_more: false comprova o início da conversa. |
| `next_request.number` | string | **Obrigatório.** Consulte o objeto ou exemplo correspondente. |
| `next_request.count` | integer | **Obrigatório.** Consulte o objeto ou exemplo correspondente. |
| `next_request.messageid` | string | **Obrigatório.** Consulte o objeto ou exemplo correspondente. |
| `next_request.timestamp` | integer | **Obrigatório.** Timestamp da âncora em milissegundos Unix. |
| `next_request.fromMe` | boolean | **Obrigatório.** Consulte o objeto ou exemplo correspondente. |
| `messages` | array / null | Lote de mensagens. |
| `messages[].id` | string | Identificador retornado pelo sistema; preserve como string. |
| `messages[].messageid` | string | ID da mensagem no WhatsApp. |
| `messages[].chatid` | string | JID do contato, grupo ou canal. |
| `messages[].sender` | string | JID do remetente. |
| `messages[].senderName` | string | Nome disponível do remetente. |
| `messages[].fromMe` | boolean | Mensagem enviada pela conta conectada. |
| `messages[].wasSentByApi` | boolean | Origem na API quando esse campo estiver presente na mensagem. |
| `messages[].isGroup` | boolean | Identifica conversa de grupo. |
| `messages[].messageType` | string | Tipo de mensagem normalizado. |
| `messages[].text` | string | Texto ou legenda disponível. |
| `messages[].messageTimestamp` | integer | Timestamp da mensagem em milissegundos Unix. |
| `messages[].content` | variável | Conteúdo específico do tipo de mensagem; não possui um formato único. |
| `messages[].status` | string | Estado disponível da mensagem; pode estar vazio. |
| `messages[].request_id` | string | Pedido manual que recuperou esta mensagem, quando correlacionado. |
| `chats` | array / null | Lote de chats. |
| `chats[].id` | string | ID local do chat. |
| `chats[].wa_chatid` | string | JID da conversa. |
| `chats[].name` | string | Nome disponível. |
| `chats[].wa_isGroup` | boolean | Conversa de grupo. |
| `chats[].wa_isBlocked` | boolean | Estado de bloqueio no snapshot. |
| `chats[].wa_archived` | boolean | Estado de arquivamento. |
| `chats[].wa_unreadCount` | integer | Quantidade de mensagens não lidas disponível. |
| `chats[].wa_label` | variável | Etiquetas associadas, conforme a projeção do chat. Pode ser texto JSON; normalize antes de usar. |
| `labels` | array / null | Lote de etiquetas. |
| `chat_labels` | array / null | Associações atualizadas pela sincronização de etiquetas. |
| `history_type` | string | Origem adicional, como labels_refresh. |
| `full_sync` | boolean | Se a sincronização de etiquetas foi completa. |
| `chunk` | object | Metadados opcionais de fragmentação. |
| `chunk.index` | integer | Índice do chunk. |
| `chunk.total` | integer | Total daquela sequência. |
| `chunk.size` | integer | Itens neste chunk. |
| `chunk.has_more` | boolean | Existem mais chunks naquela sequência. |
| `batchNumber` | integer | Número deste lote, começando em 1. |
| `batchTotal` | integer | Total de lotes desta sequência. |
| `batchChunkOrder` | integer | Sequência da origem deste lote, atribuída pela API. Não é a ordem do chunk no WhatsApp e recomeça em 1 a cada pareamento. |
| `batchHistoryStatus` | string | `loading` em lotes com dados; `complete` no lote `status` que encerra o histórico. |
| `calls` | array / null | Registros de chamadas do histórico quando event=calls. |

## Exemplos de entrega

Exemplos ilustrativos com identificadores fictícios. O conteúdo específico e os campos opcionais variam.

### Lote de mensagens

```json
{
  "EventType": "history",
  "owner": "5511999999999",
  "token": "INSTANCE_TOKEN",
  "BaseUrl": "https://seu-servidor.example",
  "instanceName": "Atendimento",
  "event": "messages",
  "request_id": "3f1c9b52-8d4e-4a7b-9c61-2f0e5d7a1b84",
  "messages": [
    {
      "id": "5511999999999:MSG_HISTORICO",
      "messageid": "MSG_HISTORICO",
      "chatid": "5511888888888@s.whatsapp.net",
      "text": "Mensagem do histórico",
      "messageTimestamp": 1788868800000
    }
  ],
  "batchNumber": 1,
  "batchTotal": 1,
  "batchChunkOrder": 1,
  "batchHistoryStatus": "loading"
}
```

### Lote de chats

```json
{
  "EventType": "history",
  "owner": "5511999999999",
  "token": "INSTANCE_TOKEN",
  "BaseUrl": "https://seu-servidor.example",
  "instanceName": "Atendimento",
  "event": "chats",
  "request_id": "3f1c9b52-8d4e-4a7b-9c61-2f0e5d7a1b84",
  "chats": [
    {
      "id": "r0123456789abcd",
      "wa_chatid": "5511888888888@s.whatsapp.net",
      "name": "Contato de exemplo"
    }
  ],
  "batchNumber": 1,
  "batchTotal": 1,
  "batchChunkOrder": 1,
  "batchHistoryStatus": "loading"
}
```

### Conclusão do histórico

```json
{
  "EventType": "history",
  "owner": "5511999999999",
  "token": "INSTANCE_TOKEN",
  "BaseUrl": "https://seu-servidor.example",
  "instanceName": "Atendimento",
  "event": "status",
  "request_id": "3f1c9b52-8d4e-4a7b-9c61-2f0e5d7a1b84",
  "batchNumber": 1,
  "batchTotal": 1,
  "batchChunkOrder": 3,
  "batchHistoryStatus": "complete"
}
```

### Resultado de POST /message/history-sync

```json
{
  "EventType": "history",
  "owner": "5511999999999",
  "token": "INSTANCE_TOKEN",
  "BaseUrl": "https://seu-servidor.example",
  "instanceName": "Atendimento",
  "event": "status",
  "request_id": "5f0c2a8e-6d1b-4c3a-9e7f-1a2b3c4d5e6f",
  "request_chat": "5511888888888@s.whatsapp.net",
  "status": "completed",
  "has_more": true,
  "history_access": "available",
  "received_messages": 50,
  "next_request": {
    "number": "5511888888888@s.whatsapp.net",
    "count": 50,
    "messageid": "3EB0MAIS_ANTIGA_RECEBIDA",
    "timestamp": 1706350400000,
    "fromMe": false
  },
  "batchChunkOrder": 4,
  "batchHistoryStatus": "complete",
  "batchNumber": 1,
  "batchTotal": 1
}
```

### Prazo do resultado vencido; histórico ainda inconclusivo

```json
{
  "EventType": "history",
  "owner": "5511999999999",
  "token": "INSTANCE_TOKEN",
  "BaseUrl": "https://seu-servidor.example",
  "instanceName": "Atendimento",
  "event": "status",
  "request_id": "5f0c2a8e-6d1b-4c3a-9e7f-1a2b3c4d5e6f",
  "request_chat": "5511888888888@s.whatsapp.net",
  "status": "timeout",
  "has_more": null,
  "history_access": "unknown",
  "received_messages": 0,
  "next_request": {
    "number": "5511888888888@s.whatsapp.net",
    "count": 50,
    "messageid": "3EB0MAIS_ANTIGA_RECEBIDA",
    "timestamp": 1706350400000,
    "fromMe": false
  },
  "batchChunkOrder": 4,
  "batchHistoryStatus": "complete",
  "batchNumber": 1,
  "batchTotal": 1
}
```

## Responder ao webhook

Seu receptor deve aceitar o POST JSON e devolver uma resposta `2xx` rapidamente, após validar e registrar o recebimento. O corpo da resposta não é um comando para a API. Processe trabalho demorado separadamente.

O worker não repete automaticamente uma entrega HTTP malsucedida. Consulte [diagnóstico de webhooks](/docs/integrations-webhooks#diagnostico) e implemente a reconciliação necessária. Eventos repetidos ou fora de ordem ainda precisam ser tolerados pelo receptor.

## Referências

- [Introdução, configuração e catálogo](/docs/integrations-webhooks)
- [Autenticação](/docs/authentication)
- [Contrato OpenAPI completo](/openapi-bundled.json)
