Skip to main content

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

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

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 (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 ids 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 ids 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

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.
Para não receber mensagens de grupo ou status de jeito nenhum, ligue ignoreGroups ou ignoreStatus nas configurações da instância:
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.

Conexão

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.