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

# Tratando eventos

> Responda rápido, deduplique, lide com a ordem e filtre o que importa para você.

## Responda rápido

Responda `2xx` em até 10 segundos e faça o trabalho de verdade depois — coloque o
evento numa fila sua e retorne. Um endpoint lento estoura o tempo, é reentregue e
recebe o mesmo evento de novo.

## Deduplique

A entrega é **at-least-once**: o mesmo evento pode chegar mais de uma vez. O jeito
mais simples de deixar isso inofensivo é guardar as mensagens pelo `messageId` e
sempre gravar com upsert.

### Mensagens

```sql theme={null}
-- MESSAGE.RECEIVED / MESSAGE.SENT
INSERT INTO messages (message_id, chat_id, from_me, sender_phone, text, status)
VALUES ($messageId, $chatId, $fromMe, $senderPhone, $text, 'SENT')
ON CONFLICT (message_id) DO NOTHING;
```

Se nenhuma linha foi inserida, você já processou aquela mensagem: não responda nem
dispare nada de novo.

Campos sem valor são omitidos do payload, então leia-os como opcionais:
`data.text ?? null` vira `NULL` na sua tabela, e `if (data.quotedMessageId)` diz se a
mensagem é uma resposta. O `sender` só existe quando outra pessoa escreveu: com
`fromMe` `true` não há `sender`, então `data.sender?.phone` é a leitura segura.

### Status

```sql theme={null}
-- MESSAGE.STATUS_UPDATED, para cada id em data.messageIds
INSERT INTO messages (message_id, status)
VALUES ($messageId, $status)
ON CONFLICT (message_id) DO UPDATE SET status = EXCLUDED.status
WHERE status_rank(messages.status) < status_rank(EXCLUDED.status);
-- status_rank: SENT = 1, DELIVERED = 2, READ = 3
```

Um status traz só `messageIds` e `status`: a conversa e o destinatário vêm do
`MESSAGE.SENT` da mesma mensagem, que já está na sua tabela.

Use upsert aqui também, não `UPDATE`: um `DELIVERED` pode chegar antes do
`MESSAGE.SENT` da mesma mensagem, e até antes de o seu sistema guardar a resposta do
envio. Um `UPDATE` não encontraria a linha e perderia o status.

O rank faz o status só avançar: um `DELIVERED` repetido, ou um `DELIVERED` que chega
depois do `READ`, não muda nada.

### Pelo `id` do evento

Todo evento também tem um [`id`](/pt-br/webhooks/events#envelope) (o header
`webhook-id`) que é idêntico em toda reentrega. Para deduplicar de forma genérica —
por exemplo, antes de colocar os eventos na sua fila — guarde os `id`s processados por
24 horas e descarte os repetidos.

O `id` identifica o **evento**, não a mensagem: o `DELIVERED` e o `READ` da mesma
mensagem têm `id`s diferentes. Não use o `messageId` para descartar eventos, ou você
vai descartar status.

## Ordem

Não há garantia de ordem. Mensagens enviadas em rajada (várias fotos de uma vez) e
eventos reentregues depois de uma falha podem chegar fora de ordem.

* Para ordenar mensagens, use o `timestamp`.
* Status só avançam (`MESSAGE.SENT` → `DELIVERED` → `READ`): ignore um status anterior ao que
  você já tem, como no upsert acima.

## Grupos

* `chat.type` é `GROUP`, `chat.id` identifica o grupo (`…@g.us`) e `sender` é o
  participante que escreveu. As suas próprias mensagens no grupo vêm com `fromMe: true`
  e sem `sender`.
* Um status significa que a mensagem chegou a ele para pelo menos um participante: o
  `READ` é "alguém leu", não "todos leram". O `DELIVERED` e o `READ` chegam uma vez
  cada por mensagem, não uma vez por participante.

## Filtrando

| Quero… | Como |
| - | - |
| Só mensagens de grupo | `chat.type === "GROUP"` |
| Mensagens de um número específico (em conversa direta ou em grupo) | Comparar o `sender.phone` (veja o aviso abaixo) |
| A conversa com um número específico | Comparar o `chat.phone` (veja o aviso abaixo) |
| Só o que eu recebi | Assinar só `MESSAGE.RECEIVED` |
| Ignorar as minhas próprias mensagens | `fromMe === false` (as suas mensagens não têm `sender`) |
| Ignorar status dos contatos | `chat.type !== "STATUS"` |
| Responder | Enviar para `to: chat.id`, exatamente como chegou |
| Chave das conversas no meu sistema | `chat.id` |
| Ligar um evento a uma mensagem que eu enviei | `messageId` — o mesmo valor que o endpoint de envio devolveu |
| Saber qual opção o contato escolheu nos meus botões ou na minha lista | A mensagem que você enviou chega como `MESSAGE.SENT` com `messageType: "INTERACTIVE"`; quando o contato toca, chega um `MESSAGE.RECEIVED` com `messageType: "BUTTON_REPLY"`, e o `buttonReply.id` é o ID que você definiu |
| Ligar um status à sua mensagem | `messageIds` — a conversa vem do `MESSAGE.SENT` daquela mensagem |

<Warning>
  O `phone` é o número exatamente como está registrado no WhatsApp. Linhas de celular
  brasileiras mais antigas vêm **sem o 9º dígito**: você envia para `5544997075632` e
  os eventos mostram `554497075632`. Ao comparar com os números que você guarda, trate
  `55` + DDD + 8 dígitos e `55` + DDD + `9` + 8 dígitos como o mesmo celular
  brasileiro. Para responder, use sempre o `chat.id` como chegou. E o `phone` não vem
  quando o WhatsApp esconde o número.
</Warning>

Para não receber mensagens de grupo ou status **de jeito nenhum**, ligue
`ignoreGroups` ou `ignoreStatus` nas configurações da instância:

```bash theme={null}
curl -X POST https://api.zapfy.io/v1/instance/settings \
  -H "Authorization: Bearer zpfy_inst_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "rejectCall": false,
    "msgRejectCall": "",
    "readMessages": false,
    "ignoreGroups": true,
    "ignoreStatus": true
  }'
```

<Warning>
  Essa chamada **substitui** todas as configurações da instância: envie o objeto
  completo. Leia os valores atuais antes com `GET /v1/instance/settings`. A
  configuração vale para todos os webhooks da instância.
</Warning>

## Conexão

| `status` do `CONNECTION.UPDATED` | O que fazer |
| - | - |
| `CONNECTED` | Nada. O `phone` diz qual número está conectado |
| `DISCONNECTED` | Nada — o número reconecta sozinho |
| `LOGGED_OUT` | Parear de novo (novo QR Code ou código de pareamento) |
| `BANNED` | Esperar `expiresIn` segundos e revisar o que causou o ban (`reason`) |

Se você monta a sua própria tela de pareamento: mostre o QR Code de cada
`QRCODE.UPDATED` com `status: GENERATED` (sempre o mais recente), ofereça tentar de
novo no `EXPIRED` e feche a tela no `CONNECTION.UPDATED` com `status: CONNECTED`.

## Envio e duplicatas

Os endpoints de envio devolvem o `messageId`; é ele que liga o seu envio aos eventos
`MESSAGE.SENT` e `MESSAGE.STATUS_UPDATED` que vêm depois. Guarde-o.

A Zapfy não deduplica requisições de envio. Se uma requisição de envio falhar sem
resposta (timeout, conexão caída), repeti-la pode enviar a mensagem duas vezes. Antes
de repetir, confira os eventos `MESSAGE.SENT` daquela conversa para ver o que de fato
saiu do seu número.


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