Webhooks
Webhooks são chamadas POST que a API faz para um endereço do seu sistema quando algo acontece: chegou uma mensagem, uma mensagem foi enviada, o número conectou ou desconectou.
Eventos
Seção intitulada “Eventos”| Evento | Quando acontece |
|---|---|
message.received |
Chegou uma mensagem |
message.sent |
Uma mensagem da fila foi enviada |
message.status |
A situação de uma mensagem mudou (inclui falhas de envio) |
presence |
Mudança de presença de um contato, como “digitando”. O corpo traz chatId e presence |
instance.connected |
O número conectou. O corpo traz phone |
instance.disconnected |
O número desconectou. O corpo traz reason e loggedOut |
contact.opted_out |
Um contato pediu para não receber mais mensagens. O corpo traz phone |
Configurar
Seção intitulada “Configurar”PUT /v1/instances/{instanceId}/webhooks substitui toda a configuração. Envie sempre o conjunto completo. Cada evento tem a sua própria URL e pode ser ligado ou desligado:
curl -X PUT https://api.zap.nigora.com.br/v1/instances/INSTANCIA_ID/webhooks \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{ "events": { "message.received": { "url": "https://meusistema.com.br/hooks/recebida", "enabled": true }, "message.status": { "url": "https://meusistema.com.br/hooks/status", "enabled": true } }, "notifySentByMe": false, "receiveFilters": { "ignoreGroups": true } }'A resposta (200) traz a configuração salva e o secret, o segredo usado na assinatura.
As URLs têm até 2.048 caracteres e precisam ser públicas. Endereços da rede interna e de loopback (como localhost) são recusados com 400 e url_not_allowed. Campos fora do formato acima também voltam 400 (validation_error). Eventos sem URL, ou com enabled: false, não geram chamadas.
Filtros de recebimento
Seção intitulada “Filtros de recebimento”Em receiveFilters, cada campo é false por padrão. Ligue para não receber esse tipo de mensagem:
ignoreGroups, ignorePrivate, ignoreText, ignoreImage, ignoreVideo, ignoreAudio, ignoreDocument.
Com notifySentByMe ligado, você também recebe message.received para as mensagens que você mesmo enviou pelo celular.
Para ler a configuração atual, use GET /webhooks. A resposta inclui o segredo de assinatura.
O que a API envia
Seção intitulada “O que a API envia”O corpo é sempre JSON, neste formato:
{ "event": "message.received", "instanceId": "…", "timestamp": "2026-11-02T14:30:00.000Z", "data": { }}data muda conforme o evento. Em message.received:
| Campo | Descrição |
|---|---|
waMessageId |
Identificador da mensagem no WhatsApp |
chatId |
Conversa, como 5511999999999@s.whatsapp.net (grupos terminam em @g.us) |
from |
Quem enviou, no mesmo formato |
fromMe |
true se foi enviada pelo próprio número |
isGroup |
true se veio de um grupo |
timestamp |
Horário da mensagem, em segundos desde 1970 |
type |
text, image, video, audio, document ou other |
text |
Texto, ou null |
quotedMessageId |
Mensagem respondida, ou null |
pushName |
Nome de exibição de quem enviou, ou null |
media |
Para mídia: url, mimeType, fileName e expiresAt |
mediaSkipped |
true se a mídia não pôde ser baixada ou era grande demais |
Em message.sent, data traz id (o devolvido no 202 do envio), waMessageId e type. Em message.status, traz id, waMessageId e status (sent, delivered, read ou failed); quando o envio falha, vem também error (veja Enviando mensagens).
Cabeçalhos
Seção intitulada “Cabeçalhos”| Cabeçalho | Conteúdo |
|---|---|
x-nigora-event |
Nome do evento |
x-nigora-delivery |
Identificador desta entrega |
x-nigora-signature |
Assinatura do corpo, no formato sha256=<hex> |
Verifique a assinatura
Seção intitulada “Verifique a assinatura”Antes de confiar numa chamada, confira a assinatura. Ela é o HMAC-SHA256 dos bytes exatos do corpo recebido, calculado com o segredo da instância (o secret de GET /webhooks) e escrito em hexadecimal, depois do prefixo sha256=.
import { createHmac, timingSafeEqual } from 'node:crypto'
// `corpoBruto` é o corpo recebido, sem passar por JSON.parse e re-serialização.function assinaturaValida(corpoBruto, cabecalho, segredo) { const esperada = 'sha256=' + createHmac('sha256', segredo).update(corpoBruto).digest('hex') const a = Buffer.from(esperada) const b = Buffer.from(cabecalho ?? '') return a.length === b.length && timingSafeEqual(a, b)}<?php$corpo = file_get_contents('php://input');$esperada = 'sha256=' . hash_hmac('sha256', $corpo, $segredo);if (!hash_equals($esperada, $_SERVER['HTTP_X_NIGORA_SIGNATURE'] ?? '')) { http_response_code(401); exit;}Para trocar o segredo, chame POST /webhooks/secret/regenerate. A resposta é { "secret": "..." } e o anterior deixa de valer. Tentativas e reenvios feitos depois da troca já saem assinados com o segredo novo.
Responda rápido e com 2xx
Seção intitulada “Responda rápido e com 2xx”Responda com qualquer status 2xx para confirmar, em até 10 segundos. Qualquer outra resposta, ou falta de resposta, conta como falha e a API tenta de novo, com espera crescente entre as tentativas (a primeira espera é de 5 segundos e ela dobra a cada falha), até 8 tentativas no total. Depois disso, a entrega fica como failed.
Faça o processamento pesado depois de responder. Confirme primeiro e trabalhe em seguida, de preferência numa fila sua.
Como pode haver reenvios, use o cabeçalho x-nigora-delivery para ignorar uma entrega que você já processou.
Histórico e reenvio
Seção intitulada “Histórico e reenvio”A API guarda as entregas dos últimos 7 dias.
| Operação | Rota |
|---|---|
| Listar entregas, da mais recente para a mais antiga | GET /webhooks/deliveries (parâmetros limit, de 1 a 100, e before, uma data ISO 8601 com fuso, para paginar) |
| Ver uma entrega: evento, situação, tentativas e conteúdo | GET /webhooks/deliveries/{id} |
| Reenviar uma entrega | POST /webhooks/deliveries/{id}/resend |
A lista vem em { "deliveries": [...] }. Cada entrega traz id, event, url, status (pending, success ou failed), attempts, lastStatusCode, lastResponse, createdAt e updatedAt. A consulta de uma entrega acrescenta o payload.
O reenvio responde 202 com { "id": "..." }, o identificador de uma nova entrega com o mesmo conteúdo, para a mesma URL. Um id que não existe nesta instância volta 404 com webhook_delivery_not_found.
O histórico é útil quando o seu sistema ficou fora do ar: liste as entregas com falha e reenvie.
Descadastro de contatos
Seção intitulada “Descadastro de contatos”Ligue o descadastro em PATCH /settings:
curl -X PATCH https://api.zap.nigora.com.br/v1/instances/INSTANCIA_ID/settings \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{ "optOut": { "enabled": true, "keywords": ["SAIR", "PARAR"] } }'Quando um contato envia, numa conversa privada, uma mensagem que seja só uma dessas palavras (sem diferenciar maiúsculas, minúsculas e acentos), ele entra na lista de descadastro e você recebe o evento contact.opted_out. Novos envios para ele são recusados com 422 (contact_opted_out). A palavra padrão é SAIR.
GET /opt-outslista os descadastrados (comlimitebeforepara paginar).DELETE /opt-outs/{phone}permite voltar a enviar para um telefone. Responde204, mesmo que o telefone não estivesse na lista.